mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
refactor: financial statements to nestjs
This commit is contained in:
@@ -8,9 +8,11 @@ import { TransactionsByVendorModule } from './modules/TransactionsByVendor/Trans
|
||||
import { TransactionsByCustomerModule } from './modules/TransactionsByCustomer/TransactionsByCustomer.module';
|
||||
import { TransactionsByReferenceModule } from './modules/TransactionsByReference/TransactionByReference.module';
|
||||
import { ARAgingSummaryModule } from './modules/ARAgingSummary/ARAgingSummary.module';
|
||||
// import { APAgingSummaryModule } from './modules/APAgingSummary/APAgingSummary.module';
|
||||
import { APAgingSummaryModule } from './modules/APAgingSummary/APAgingSummary.module';
|
||||
import { InventoryItemDetailsModule } from './modules/InventoryItemDetails/InventoryItemDetails.module';
|
||||
import { InventoryValuationSheetModule } from './modules/InventoryValuationSheet/InventoryValuationSheet.module';
|
||||
import { SalesTaxLiabilityModule } from './modules/SalesTaxLiabilitySummary/SalesTaxLiability.module';
|
||||
import { JournalSheetModule } from './modules/JournalSheet/JournalSheet.module';
|
||||
|
||||
@Module({
|
||||
providers: [],
|
||||
@@ -24,9 +26,11 @@ import { InventoryValuationSheetModule } from './modules/InventoryValuationSheet
|
||||
TransactionsByCustomerModule,
|
||||
TransactionsByReferenceModule,
|
||||
ARAgingSummaryModule,
|
||||
// APAgingSummaryModule,
|
||||
APAgingSummaryModule,
|
||||
InventoryItemDetailsModule,
|
||||
InventoryValuationSheetModule,
|
||||
SalesTaxLiabilityModule,
|
||||
JournalSheetModule,
|
||||
],
|
||||
})
|
||||
export class FinancialStatementsModule {}
|
||||
|
||||
@@ -7,18 +7,22 @@ import { APAgingSummaryPdfInjectable } from './APAgingSummaryPdfInjectable';
|
||||
import { APAgingSummaryRepository } from './APAgingSummaryRepository';
|
||||
import { APAgingSummaryApplication } from './APAgingSummaryApplication';
|
||||
import { APAgingSummaryController } from './APAgingSummary.controller';
|
||||
import { APAgingSummaryMeta } from './APAgingSummaryMeta';
|
||||
import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module';
|
||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
|
||||
@Module({
|
||||
imports: [AgingSummaryModule],
|
||||
imports: [AgingSummaryModule, FinancialSheetCommonModule],
|
||||
providers: [
|
||||
APAgingSummaryService,
|
||||
APAgingSummaryMeta,
|
||||
APAgingSummaryTableInjectable,
|
||||
APAgingSummaryExportInjectable,
|
||||
APAgingSummaryPdfInjectable,
|
||||
APAgingSummaryRepository,
|
||||
APAgingSummaryApplication
|
||||
APAgingSummaryApplication,
|
||||
TenancyContext,
|
||||
],
|
||||
controllers: [APAgingSummaryController],
|
||||
})
|
||||
export class APAgingSummaryModule {}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { isEmpty, groupBy } from 'lodash';
|
||||
import { Bill } from '@/modules/Bills/models/Bill';
|
||||
import { Vendor } from '@/modules/Vendors/models/Vendor';
|
||||
import { IAPAgingSummaryQuery } from './APAgingSummary.types';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import { groupBy } from 'ramda';
|
||||
import { IAPAgingSummaryQuery } from './APAgingSummary.types';
|
||||
import { ModelObject } from 'objection';
|
||||
|
||||
export class APAgingSummaryRepository {
|
||||
@Inject(Vendor.name)
|
||||
@@ -32,19 +32,19 @@ export class APAgingSummaryRepository {
|
||||
* Due bills by vendor id.
|
||||
* @param {Record<string, Bill[]>} dueBillsByVendorId
|
||||
*/
|
||||
dueBillsByVendorId: Record<number, Bill[]>;
|
||||
dueBillsByVendorId: Record<string, Bill[]>;
|
||||
|
||||
/**
|
||||
* Overdue bills.
|
||||
* @param {Bill[]} overdueBills
|
||||
* @param {Bill[]} overdueBills - overdue bills.
|
||||
*/
|
||||
overdueBills: Bill[];
|
||||
overdueBills: ModelObject<Bill>[];
|
||||
|
||||
/**
|
||||
* Overdue bills by vendor id.
|
||||
* @param {Record<string, Bill[]>} overdueBillsByVendorId
|
||||
* @param {Record<string, Bill[]>} overdueBillsByVendorId - Overdue bills by vendor id.
|
||||
*/
|
||||
overdueBillsByVendorId: Record<number, Bill[]>;
|
||||
overdueBillsByVendorId: ModelObject<Bill>[];
|
||||
|
||||
/**
|
||||
* Vendors.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { I18nService } from 'nestjs-i18n';
|
||||
import {
|
||||
IAPAgingSummaryQuery,
|
||||
IAPAgingSummaryTable,
|
||||
@@ -8,18 +9,21 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class APAgingSummaryTableInjectable {
|
||||
constructor(private readonly APAgingSummarySheet: APAgingSummaryService) {}
|
||||
constructor(
|
||||
private readonly APAgingSummarySheet: APAgingSummaryService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves A/P aging summary in table format.
|
||||
* @param {IAPAgingSummaryQuery} query -
|
||||
* @param {IAPAgingSummaryQuery} query -
|
||||
* @returns {Promise<IAPAgingSummaryTable>}
|
||||
*/
|
||||
public async table(
|
||||
query: IAPAgingSummaryQuery,
|
||||
): Promise<IAPAgingSummaryTable> {
|
||||
const report = await this.APAgingSummarySheet.APAgingSummary(query);
|
||||
const table = new APAgingSummaryTable(report.data, query, {});
|
||||
const table = new APAgingSummaryTable(report.data, query, this.i18nService);
|
||||
|
||||
return {
|
||||
table: {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Controller, Get, Headers, Query, Res } from '@nestjs/common';
|
||||
import { IJournalReportQuery } from './JournalSheet.types';
|
||||
import { Response } from 'express';
|
||||
import { AcceptType } from '@/constants/accept-type';
|
||||
import { JournalSheetApplication } from './JournalSheetApplication';
|
||||
|
||||
@Controller('/reports/journal')
|
||||
export class JournalSheetController {
|
||||
constructor(private readonly journalSheetApp: JournalSheetApplication) {}
|
||||
|
||||
@Get('/')
|
||||
async journalSheet(
|
||||
@Query() query: IJournalReportQuery,
|
||||
@Res() res: Response,
|
||||
@Headers('accept') acceptHeader: string,
|
||||
) {
|
||||
// Retrieves the json table format.
|
||||
if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
||||
const table = await this.journalSheetApp.table(query);
|
||||
return res.status(200).send(table);
|
||||
|
||||
// Retrieves the csv format.
|
||||
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
||||
const buffer = await this.journalSheetApp.csv(query);
|
||||
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||
res.setHeader('Content-Type', 'text/csv');
|
||||
|
||||
return res.send(buffer);
|
||||
// Retrieves the xlsx format.
|
||||
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
||||
const buffer = await this.journalSheetApp.xlsx(query);
|
||||
|
||||
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 if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
||||
const pdfContent = await this.journalSheetApp.pdf(query);
|
||||
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Length': pdfContent.length,
|
||||
});
|
||||
res.send(pdfContent);
|
||||
} else {
|
||||
const sheet = await this.journalSheetApp.sheet(query);
|
||||
|
||||
return res.status(200).send(sheet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { JournalSheetController } from './JournalSheet.controller';
|
||||
import { JournalSheetApplication } from './JournalSheetApplication';
|
||||
import { JournalSheetPdfInjectable } from './JournalSheetPdfInjectable';
|
||||
import { JournalSheetExportInjectable } from './JournalSheetExport';
|
||||
import { JournalSheetService } from './JournalSheetService';
|
||||
import { JournalSheetTableInjectable } from './JournalSheetTableInjectable';
|
||||
import { JournalSheetRepository } from './JournalSheetRepository';
|
||||
import { JournalSheetMeta } from './JournalSheetMeta';
|
||||
import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module';
|
||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import { AccountsModule } from '@/modules/Accounts/Accounts.module';
|
||||
|
||||
@Module({
|
||||
imports: [FinancialSheetCommonModule, AccountsModule],
|
||||
controllers: [JournalSheetController],
|
||||
providers: [
|
||||
JournalSheetApplication,
|
||||
JournalSheetTableInjectable,
|
||||
JournalSheetService,
|
||||
JournalSheetExportInjectable,
|
||||
JournalSheetPdfInjectable,
|
||||
JournalSheetRepository,
|
||||
JournalSheetMeta,
|
||||
TenancyContext,
|
||||
],
|
||||
})
|
||||
export class JournalSheetModule {}
|
||||
@@ -0,0 +1,135 @@
|
||||
import { I18nService } from 'nestjs-i18n';
|
||||
import { sumBy, chain, get, head } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
IJournalReportEntriesGroup,
|
||||
IJournalReportQuery,
|
||||
IJournalSheetEntry,
|
||||
IJournalTableData,
|
||||
} from './JournalSheet.types';
|
||||
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||
import { JournalSheetRepository } from './JournalSheetRepository';
|
||||
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||
|
||||
export class JournalSheet extends FinancialSheet {
|
||||
readonly ledger: Ledger;
|
||||
readonly query: IJournalReportQuery;
|
||||
readonly repository: JournalSheetRepository;
|
||||
readonly i18n: I18nService;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
*/
|
||||
constructor(
|
||||
query: IJournalReportQuery,
|
||||
repository: JournalSheetRepository,
|
||||
i18n: I18nService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.query = query;
|
||||
this.repository = repository;
|
||||
this.numberFormat = {
|
||||
...this.numberFormat,
|
||||
...this.query.numberFormat,
|
||||
};
|
||||
this.i18n = i18n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry mapper.
|
||||
* @param {ILedgerEntry} entry
|
||||
*/
|
||||
entryMapper(entry: ILedgerEntry): IJournalSheetEntry {
|
||||
const account = this.repository.accountsGraph.getNodeData(entry.accountId);
|
||||
const contact = this.repository.contactsById.get(entry.contactId);
|
||||
|
||||
return {
|
||||
entryId: entry.id,
|
||||
|
||||
index: entry.index,
|
||||
note: entry.note,
|
||||
|
||||
contactName: get(contact, 'displayName'),
|
||||
contactType: get(contact, 'contactService'),
|
||||
|
||||
accountName: account.name,
|
||||
accountCode: account.code,
|
||||
transactionNumber: entry.transactionNumber,
|
||||
|
||||
currencyCode: this.baseCurrency,
|
||||
formattedCredit: this.formatNumber(entry.credit),
|
||||
formattedDebit: this.formatNumber(entry.debit),
|
||||
|
||||
credit: entry.credit,
|
||||
debit: entry.debit,
|
||||
|
||||
createdAt: entry.createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* maps the journal entries.
|
||||
* @param {IJournalEntry[]} entries -
|
||||
*/
|
||||
entriesMapper(entries: ILedgerEntry[]): Array<IJournalSheetEntry> {
|
||||
return entries.map(this.entryMapper.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping journal entries groups.
|
||||
* @param {ILedgerEntry[]} entriesGroup -
|
||||
* @param {ILedgerEntry} key -
|
||||
* @return {IJournalReportEntriesGroup}
|
||||
*/
|
||||
entriesGroupsMapper(
|
||||
entriesGroup: ILedgerEntry[],
|
||||
groupEntry: ILedgerEntry,
|
||||
): IJournalReportEntriesGroup {
|
||||
const totalCredit = sumBy(entriesGroup, 'credit');
|
||||
const totalDebit = sumBy(entriesGroup, 'debit');
|
||||
|
||||
return {
|
||||
date: moment(groupEntry.date).toDate(),
|
||||
dateFormatted: moment(groupEntry.date).format('YYYY MMM DD'),
|
||||
|
||||
transactionType: groupEntry.transactionType,
|
||||
referenceId: groupEntry.transactionId,
|
||||
referenceTypeFormatted: this.i18n.t(groupEntry.transactionType),
|
||||
|
||||
entries: this.entriesMapper(entriesGroup),
|
||||
|
||||
currencyCode: this.baseCurrency,
|
||||
|
||||
credit: totalCredit,
|
||||
debit: totalDebit,
|
||||
|
||||
formattedCredit: this.formatTotalNumber(totalCredit),
|
||||
formattedDebit: this.formatTotalNumber(totalDebit),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping the journal entries to entries groups.
|
||||
* @param {IJournalEntry[]} entries
|
||||
* @return {IJournalReportEntriesGroup[]}
|
||||
*/
|
||||
entriesWalker(entries: ILedgerEntry[]): IJournalReportEntriesGroup[] {
|
||||
return chain(entries)
|
||||
.groupBy((entry) => `${entry.referenceId}-${entry.referenceType}`)
|
||||
.map((entriesGroup: ILedgerEntry[], key: string) => {
|
||||
const headEntry = head(entriesGroup);
|
||||
return this.entriesGroupsMapper(entriesGroup, headEntry);
|
||||
})
|
||||
.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve journal report.
|
||||
* @return {IJournalReport}
|
||||
*/
|
||||
reportData(): IJournalTableData {
|
||||
return this.entriesWalker(this.ledger.entries);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { IFinancialSheetCommonMeta } from '../../types/Report.types';
|
||||
import { IFinancialTable } from '../../types/Table.types';
|
||||
|
||||
export interface IJournalReportQuery {
|
||||
fromDate: Date | string;
|
||||
toDate: Date | string;
|
||||
numberFormat: {
|
||||
noCents: boolean;
|
||||
divideOn1000: boolean;
|
||||
};
|
||||
transactionType: string;
|
||||
transactionId: string;
|
||||
|
||||
accountsIds: number | number[];
|
||||
fromRange: number;
|
||||
toRange: number;
|
||||
}
|
||||
|
||||
export interface IJournalSheetEntry {
|
||||
entryId: number;
|
||||
index: number;
|
||||
|
||||
credit: number;
|
||||
debit: number;
|
||||
|
||||
formattedDebit: string;
|
||||
formattedCredit: string;
|
||||
|
||||
contactType: string;
|
||||
contactName: string;
|
||||
|
||||
currencyCode: string;
|
||||
|
||||
accountName: string;
|
||||
accountCode: string;
|
||||
transactionNumber: string;
|
||||
|
||||
note: string;
|
||||
createdAt: Date | string;
|
||||
}
|
||||
|
||||
export interface IJournalReportEntriesGroup {
|
||||
date: Date;
|
||||
dateFormatted: string;
|
||||
|
||||
entries: IJournalSheetEntry[];
|
||||
currencyCode: string;
|
||||
|
||||
credit: number;
|
||||
debit: number;
|
||||
|
||||
formattedCredit: string;
|
||||
formattedDebit: string;
|
||||
|
||||
transactionType: string;
|
||||
|
||||
referenceId: number;
|
||||
referenceTypeFormatted: string;
|
||||
}
|
||||
|
||||
export interface IJournalReport {
|
||||
entries: IJournalReportEntriesGroup[];
|
||||
}
|
||||
|
||||
export interface IJournalSheetMeta extends IFinancialSheetCommonMeta {
|
||||
formattedDateRange: string;
|
||||
formattedFromDate: string;
|
||||
formattedToDate: string;
|
||||
}
|
||||
|
||||
export interface IJournalTable extends IFinancialTable {
|
||||
query: IJournalReportQuery;
|
||||
meta: IJournalSheetMeta;
|
||||
}
|
||||
|
||||
export type IJournalTableData = IJournalReportEntriesGroup[];
|
||||
|
||||
export interface IJournalSheet {
|
||||
data: IJournalTableData;
|
||||
query: IJournalReportQuery;
|
||||
meta: IJournalSheetMeta;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { JournalSheetService } from './JournalSheetService';
|
||||
import { JournalSheetTableInjectable } from './JournalSheetTableInjectable';
|
||||
import { JournalSheetExportInjectable } from './JournalSheetExport';
|
||||
import { JournalSheetPdfInjectable } from './JournalSheetPdfInjectable';
|
||||
import { IJournalReportQuery, IJournalTable } from './JournalSheet.types';
|
||||
|
||||
export class JournalSheetApplication {
|
||||
constructor(
|
||||
private readonly journalSheetTable: JournalSheetTableInjectable,
|
||||
private readonly journalSheet: JournalSheetService,
|
||||
private readonly journalExport: JournalSheetExportInjectable,
|
||||
private readonly journalPdf: JournalSheetPdfInjectable,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the journal sheet.
|
||||
* @param {IJournalReportQuery} query
|
||||
* @returns {}
|
||||
*/
|
||||
public sheet(query: IJournalReportQuery) {
|
||||
return this.journalSheet.journalSheet(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the journal sheet in table format.
|
||||
* @param {IJournalReportQuery} query
|
||||
* @returns {Promise<IJournalTable>}
|
||||
*/
|
||||
public table(query: IJournalReportQuery): Promise<IJournalTable> {
|
||||
return this.journalSheetTable.table(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the journal sheet in xlsx format.
|
||||
* @param {IJournalReportQuery} query
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public xlsx(query: IJournalReportQuery) {
|
||||
return this.journalExport.xlsx(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the journal sheet in csv format.
|
||||
* @param {IJournalReportQuery} query
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
public csv(query: IJournalReportQuery) {
|
||||
return this.journalExport.csv(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the journal sheet in pdf format.
|
||||
* @param {IJournalReportQuery} query
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public pdf(query: IJournalReportQuery) {
|
||||
return this.journalPdf.pdf(query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { IJournalReportQuery } from './JournalSheet.types';
|
||||
import { JournalSheetTableInjectable } from './JournalSheetTableInjectable';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { TableSheet } from '../../common/TableSheet';
|
||||
|
||||
@Injectable()
|
||||
export class JournalSheetExportInjectable {
|
||||
constructor(
|
||||
private readonly journalSheetTable: JournalSheetTableInjectable,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the trial balance sheet in XLSX format.
|
||||
* @param {IJournalReportQuery} query - Journal report query.
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public async xlsx(query: IJournalReportQuery) {
|
||||
const table = await this.journalSheetTable.table(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 {IJournalReportQuery} query - Journal report query.
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public async csv(query: IJournalReportQuery): Promise<string> {
|
||||
const table = await this.journalSheetTable.table(query);
|
||||
|
||||
const tableSheet = new TableSheet(table.table);
|
||||
const tableCsv = tableSheet.convertToCSV();
|
||||
|
||||
return tableCsv;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import moment from 'moment';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { FinancialSheetMeta } from '../../common/FinancialSheetMeta';
|
||||
import { IJournalReportQuery, IJournalSheetMeta } from './JournalSheet.types';
|
||||
|
||||
@Injectable()
|
||||
export class JournalSheetMeta {
|
||||
constructor(private readonly financialSheetMeta: FinancialSheetMeta) {}
|
||||
|
||||
/**
|
||||
* Retrieves the journal sheet meta.
|
||||
* @param {IJournalReportQuery} query -
|
||||
* @returns {Promise<IJournalSheetMeta>}
|
||||
*/
|
||||
public async meta(
|
||||
query: IJournalReportQuery,
|
||||
): Promise<IJournalSheetMeta> {
|
||||
const common = await this.financialSheetMeta.meta();
|
||||
|
||||
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD');
|
||||
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD');
|
||||
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`;
|
||||
|
||||
return {
|
||||
...common,
|
||||
formattedDateRange,
|
||||
formattedFromDate,
|
||||
formattedToDate,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { JournalSheetTableInjectable } from './JournalSheetTableInjectable';
|
||||
import { HtmlTableCustomCss } from './constant';
|
||||
import { TableSheetPdf } from '../../common/TableSheetPdf';
|
||||
import { IJournalReportQuery } from './JournalSheet.types';
|
||||
|
||||
@Injectable()
|
||||
export class JournalSheetPdfInjectable {
|
||||
constructor(
|
||||
private readonly journalSheetTable: JournalSheetTableInjectable,
|
||||
private readonly tableSheetPdf: TableSheetPdf,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Converts the given journal sheet table to pdf.
|
||||
* @param {number} tenantId - Tenant ID.
|
||||
* @param {IBalanceSheetQuery} query - Balance sheet query.
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public async pdf(query: IJournalReportQuery): Promise<Buffer> {
|
||||
const table = await this.journalSheetTable.table(query);
|
||||
|
||||
return this.tableSheetPdf.convertToPdf(
|
||||
table.table,
|
||||
table.meta.sheetName,
|
||||
table.meta.formattedDateRange,
|
||||
HtmlTableCustomCss,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
||||
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
|
||||
import { Contact } from '@/modules/Contacts/models/Contact';
|
||||
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import { transformToMap } from '@/utils/transform-to-key';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { ModelObject } from 'objection';
|
||||
|
||||
export class JournalSheetRepository {
|
||||
@Inject(TenancyContext)
|
||||
private tenancyContext: TenancyContext;
|
||||
|
||||
@Inject(AccountRepository)
|
||||
private accountRepository: AccountRepository;
|
||||
|
||||
@Inject(Contact.name)
|
||||
private contactModel: typeof Contact;
|
||||
|
||||
@Inject(AccountTransaction.name)
|
||||
private accountTransaction: typeof AccountTransaction;
|
||||
|
||||
@Inject(AccountTransaction.name)
|
||||
private accountTransactions: Array<ModelObject<AccountTransaction>>;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public filter: any;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public accountsGraph: any;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public contacts: Array<ModelObject<Contact>>
|
||||
|
||||
/**
|
||||
* Contacts by id map.
|
||||
*/
|
||||
public contactsById: Map<number, ModelObject<Contact>>;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public ledger: Ledger;
|
||||
|
||||
public baseCurrency: string;
|
||||
|
||||
setFilter(filter: any) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the journal sheet data.
|
||||
*/
|
||||
async load() {
|
||||
await this.initBaseCurrency();
|
||||
await this.initAccountsGraph();
|
||||
await this.initAccountTransactions();
|
||||
await this.initContacts();
|
||||
await this.initLedger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize base currency.
|
||||
*/
|
||||
async initBaseCurrency () {
|
||||
const metadata = await this.tenancyContext.getTenantMetadata();
|
||||
|
||||
this.baseCurrency = metadata.baseCurrency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize accounts graph.
|
||||
*/
|
||||
async initAccountsGraph() {
|
||||
// Retrieve all accounts on the storage.
|
||||
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||
this.accountsGraph = accountsGraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize account transactions.
|
||||
*/
|
||||
async initAccountTransactions() {
|
||||
// Retrieve all journal transactions based on the given query.
|
||||
const transactions = await this.accountTransaction
|
||||
.query()
|
||||
.onBuild((query) => {
|
||||
if (this.filter.fromRange || this.filter.toRange) {
|
||||
query.modify(
|
||||
'filterAmountRange',
|
||||
this.filter.fromRange,
|
||||
this.filter.toRange,
|
||||
);
|
||||
}
|
||||
query.modify(
|
||||
'filterDateRange',
|
||||
this.filter.fromDate,
|
||||
this.filter.toDate,
|
||||
);
|
||||
query.orderBy(['date', 'createdAt', 'indexGroup', 'index']);
|
||||
|
||||
if (this.filter.transactionType) {
|
||||
query.where('reference_type', this.filter.transactionType);
|
||||
}
|
||||
if (this.filter.transactionType && this.filter.transactionId) {
|
||||
query.where('reference_id', this.filter.transactionId);
|
||||
}
|
||||
});
|
||||
this.accountTransactions = transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize contacts.
|
||||
*/
|
||||
async initContacts() {
|
||||
const contacts = await this.contactModel.query();
|
||||
|
||||
this.contacts = contacts;
|
||||
this.contactsById = transformToMap(contacts, 'id');
|
||||
}
|
||||
|
||||
async initLedger(){
|
||||
this.ledger = Ledger.fromTransactions(this.accountTransactions);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { I18nService } from 'nestjs-i18n';
|
||||
import { JournalSheet } from './JournalSheet';
|
||||
import { JournalSheetMeta } from './JournalSheetMeta';
|
||||
import { getJournalSheetDefaultQuery } from './constant';
|
||||
import { IJournalReportQuery, IJournalSheet } from './JournalSheet.types';
|
||||
import { events } from '@/common/events/events';
|
||||
import { JournalSheetRepository } from './JournalSheetRepository';
|
||||
|
||||
@Injectable()
|
||||
export class JournalSheetService {
|
||||
constructor(
|
||||
private readonly journalSheetMeta: JournalSheetMeta,
|
||||
private readonly journalRepository: JournalSheetRepository,
|
||||
private readonly eventPublisher: EventEmitter2,
|
||||
private readonly i18n: I18nService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Journal sheet.
|
||||
* @param {IJournalReportQuery} query - Journal sheet query.
|
||||
* @returns {Promise<IJournalSheet>}
|
||||
*/
|
||||
async journalSheet(query: IJournalReportQuery): Promise<IJournalSheet> {
|
||||
const filter = {
|
||||
...getJournalSheetDefaultQuery(),
|
||||
...query,
|
||||
};
|
||||
this.journalRepository.setFilter(query);
|
||||
await this.journalRepository.load();
|
||||
|
||||
// Journal report instance.
|
||||
const journalSheetInstance = new JournalSheet(
|
||||
filter,
|
||||
this.journalRepository,
|
||||
this.i18n,
|
||||
);
|
||||
// Retrieve journal report columns.
|
||||
const journalSheetData = journalSheetInstance.reportData();
|
||||
|
||||
// Retrieve the journal sheet meta.
|
||||
const meta = await this.journalSheetMeta.meta(filter);
|
||||
|
||||
// Triggers `onJournalViewed` event.
|
||||
await this.eventPublisher.emitAsync(events.reports.onJournalViewed, {
|
||||
query,
|
||||
});
|
||||
|
||||
return {
|
||||
data: journalSheetData,
|
||||
query: filter,
|
||||
meta,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
import * as R from 'ramda';
|
||||
import { first } from 'lodash';
|
||||
import { I18nService } from 'nestjs-i18n';
|
||||
import {
|
||||
IJournalReportEntriesGroup,
|
||||
IJournalReportQuery,
|
||||
IJournalSheetEntry,
|
||||
IJournalTableData,
|
||||
} from './JournalSheet.types';
|
||||
import { ROW_TYPE } from './types';
|
||||
import { FinancialTable } from '../../common/FinancialTable';
|
||||
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
|
||||
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||
import {
|
||||
IColumnMapperMeta,
|
||||
ITableColumn,
|
||||
ITableColumnAccessor,
|
||||
ITableRow,
|
||||
} from '../../types/Table.types';
|
||||
import { tableRowMapper } from '../../utils/Table.utils';
|
||||
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||
|
||||
export class JournalSheetTable extends R.pipe(
|
||||
FinancialTable,
|
||||
FinancialSheetStructure,
|
||||
)(FinancialSheet) {
|
||||
data: IJournalTableData;
|
||||
query: IJournalReportQuery;
|
||||
i18n: any;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {IJournalTableData} data -
|
||||
* @param {IJournalReportQuery} query -
|
||||
* @param {I18nService} i18n - I18n service.
|
||||
*/
|
||||
constructor(
|
||||
data: IJournalTableData,
|
||||
query: IJournalReportQuery,
|
||||
i18n: I18nService,
|
||||
) {
|
||||
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: 'debit', accessor: 'entry.formattedDebit' },
|
||||
{ key: 'credit', accessor: 'entry.formattedCredit' },
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* 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: 'debit', accessor: 'formattedDebit' },
|
||||
{ key: 'credit', accessor: 'formattedCredit' },
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* 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: 'debit', accessor: 'formattedDebit' },
|
||||
{ key: 'credit', accessor: 'formattedCredit' },
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* 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: 'debit', value: '' },
|
||||
{ key: 'credit', 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: 'debit', label: 'Debit' },
|
||||
{ key: 'credit', label: 'Credit' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: IJournalSheetEntry): 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 ILedgerEntry, 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,33 @@
|
||||
import { JournalSheetService } from './JournalSheetService';
|
||||
import { IJournalReportQuery, IJournalTable } from './JournalSheet.types';
|
||||
import { JournalSheetTable } from './JournalSheetTable';
|
||||
import { I18nService } from 'nestjs-i18n';
|
||||
|
||||
export class JournalSheetTableInjectable {
|
||||
constructor(
|
||||
private readonly journalSheetService: JournalSheetService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the journal sheet in table format.
|
||||
* @param {IJournalReportQuery} query - Journal report query.
|
||||
* @returns {Promise<IJournalTable>}
|
||||
*/
|
||||
public async table(query: IJournalReportQuery): Promise<IJournalTable> {
|
||||
const journal = await this.journalSheetService.journalSheet(query);
|
||||
const table = new JournalSheetTable(
|
||||
journal.data,
|
||||
journal.query,
|
||||
this.i18nService,
|
||||
);
|
||||
return {
|
||||
table: {
|
||||
columns: table.tableColumns(),
|
||||
rows: table.tableData(),
|
||||
},
|
||||
query: journal.query,
|
||||
meta: journal.meta,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export const HtmlTableCustomCss = `
|
||||
table tr.row-type--total td{
|
||||
font-weight: 600;
|
||||
}
|
||||
table tr td:not(:first-child) {
|
||||
border-left: 1px solid #ececec;
|
||||
}
|
||||
table tr:last-child td {
|
||||
border-bottom: 1px solid #ececec;
|
||||
}
|
||||
table .cell--credit,
|
||||
table .cell--debit,
|
||||
table .column--credit,
|
||||
table .column--debit{
|
||||
text-align: right;
|
||||
}
|
||||
`;
|
||||
|
||||
export const getJournalSheetDefaultQuery = () => ({
|
||||
fromDate: moment().startOf('month').format('YYYY-MM-DD'),
|
||||
toDate: moment().format('YYYY-MM-DD'),
|
||||
fromRange: null,
|
||||
toRange: null,
|
||||
accountsIds: [],
|
||||
numberFormat: {
|
||||
noCents: false,
|
||||
divideOn1000: false,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
export enum ROW_TYPE {
|
||||
ENTRY = 'ENTRY',
|
||||
TOTAL = 'TOTAL'
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SalesTaxLiabiltiySummaryPdf } from './SalesTaxLiabiltiySummaryPdf';
|
||||
import { SalesTaxLiabilitySummaryTableInjectable } from './SalesTaxLiabilitySummaryTableInjectable';
|
||||
import { SalesTaxLiabilitySummaryExportInjectable } from './SalesTaxLiabilitySummaryExportInjectable';
|
||||
import { SalesTaxLiabilitySummaryService } from './SalesTaxLiabilitySummaryService';
|
||||
import { SalesTaxLiabilitySummaryApplication } from './SalesTaxLiabilitySummaryApplication';
|
||||
import { SalesTaxLiabilitySummaryController } from './SalesTaxLiabilitySummary.controller';
|
||||
import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module';
|
||||
import { SalesTaxLiabilitySummaryRepository } from './SalesTaxLiabilitySummaryRepository';
|
||||
import { SalesTaxLiabilitySummaryMeta } from './SalesTaxLiabilitySummaryMeta';
|
||||
|
||||
@Module({
|
||||
imports: [FinancialSheetCommonModule],
|
||||
providers: [
|
||||
SalesTaxLiabiltiySummaryPdf,
|
||||
SalesTaxLiabilitySummaryTableInjectable,
|
||||
SalesTaxLiabilitySummaryExportInjectable,
|
||||
SalesTaxLiabilitySummaryService,
|
||||
SalesTaxLiabilitySummaryRepository,
|
||||
SalesTaxLiabilitySummaryMeta,
|
||||
SalesTaxLiabilitySummaryApplication,
|
||||
],
|
||||
controllers: [SalesTaxLiabilitySummaryController],
|
||||
})
|
||||
export class SalesTaxLiabilityModule {}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { IFinancialSheetCommonMeta } from '../../types/Report.types';
|
||||
import { IFinancialTable } from '../../types/Table.types';
|
||||
|
||||
export interface SalesTaxLiabilitySummaryQuery {
|
||||
fromDate: Date;
|
||||
toDate: Date;
|
||||
basis: 'cash' | 'accrual';
|
||||
}
|
||||
|
||||
export interface SalesTaxLiabilitySummaryAmount {
|
||||
amount: number;
|
||||
formattedAmount: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface SalesTaxLiabilitySummaryTotal {
|
||||
taxableAmount: SalesTaxLiabilitySummaryAmount;
|
||||
taxAmount: SalesTaxLiabilitySummaryAmount;
|
||||
collectedTaxAmount: SalesTaxLiabilitySummaryAmount;
|
||||
}
|
||||
|
||||
export interface SalesTaxLiabilitySummaryRate {
|
||||
id: number;
|
||||
taxName: string;
|
||||
taxableAmount: SalesTaxLiabilitySummaryAmount;
|
||||
taxAmount: SalesTaxLiabilitySummaryAmount;
|
||||
taxPercentage: any;
|
||||
collectedTaxAmount: SalesTaxLiabilitySummaryAmount;
|
||||
}
|
||||
|
||||
export enum SalesTaxLiabilitySummaryTableRowType {
|
||||
TaxRate = 'TaxRate',
|
||||
Total = 'Total',
|
||||
}
|
||||
|
||||
export interface SalesTaxLiabilitySummaryReportData {
|
||||
taxRates: SalesTaxLiabilitySummaryRate[];
|
||||
total: SalesTaxLiabilitySummaryTotal;
|
||||
}
|
||||
|
||||
export type SalesTaxLiabilitySummaryPayableById = Record<
|
||||
string,
|
||||
{ taxRateId: number; credit: number; debit: number }
|
||||
>;
|
||||
|
||||
export type SalesTaxLiabilitySummarySalesById = Record<
|
||||
string,
|
||||
{ taxRateId: number; credit: number; debit: number }
|
||||
>;
|
||||
|
||||
export interface SalesTaxLiabilitySummaryMeta
|
||||
extends IFinancialSheetCommonMeta {
|
||||
formattedFromDate: string;
|
||||
formattedToDate: string;
|
||||
formattedDateRange: string;
|
||||
}
|
||||
|
||||
export interface ISalesTaxLiabilitySummaryTable extends IFinancialTable {
|
||||
query: SalesTaxLiabilitySummaryQuery;
|
||||
meta: SalesTaxLiabilitySummaryMeta;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Controller, Get, Headers, Query, Res } from '@nestjs/common';
|
||||
import { SalesTaxLiabilitySummaryQuery } from './SalesTaxLiability.types';
|
||||
import { AcceptType } from '@/constants/accept-type';
|
||||
import { SalesTaxLiabilitySummaryApplication } from './SalesTaxLiabilitySummaryApplication';
|
||||
import { Response } from 'express';
|
||||
import { PublicRoute } from '@/modules/Auth/Jwt.guard';
|
||||
|
||||
@Controller('/reports/sales-tax-liability-summary')
|
||||
@PublicRoute()
|
||||
export class SalesTaxLiabilitySummaryController {
|
||||
constructor(
|
||||
private readonly salesTaxLiabilitySummaryApp: SalesTaxLiabilitySummaryApplication,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
public async getSalesTaxLiabilitySummary(
|
||||
@Query() query: SalesTaxLiabilitySummaryQuery,
|
||||
@Res() res: Response,
|
||||
@Headers('accept') acceptHeader: string,
|
||||
) {
|
||||
// Retrieves the json table format.
|
||||
if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
||||
const table = await this.salesTaxLiabilitySummaryApp.table(query);
|
||||
return res.status(200).send(table);
|
||||
// Retrieves the xlsx format.
|
||||
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
||||
const buffer = await this.salesTaxLiabilitySummaryApp.xlsx(query);
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||
res.setHeader(
|
||||
'Content-Type',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
);
|
||||
return res.send(buffer);
|
||||
// Retrieves the csv format.
|
||||
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
||||
const buffer = await this.salesTaxLiabilitySummaryApp.csv(query);
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||
res.setHeader('Content-Type', 'text/csv');
|
||||
|
||||
return res.send(buffer);
|
||||
// Retrieves the json format.
|
||||
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
||||
const pdfContent = await this.salesTaxLiabilitySummaryApp.pdf(query);
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Length': pdfContent.length,
|
||||
});
|
||||
return res.status(200).send(pdfContent);
|
||||
} else {
|
||||
const sheet = await this.salesTaxLiabilitySummaryApp.sheet(query);
|
||||
return res.status(200).send(sheet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import * as R from 'ramda';
|
||||
import { isEmpty, sumBy } from 'lodash';
|
||||
import {
|
||||
SalesTaxLiabilitySummaryQuery,
|
||||
SalesTaxLiabilitySummaryRate,
|
||||
SalesTaxLiabilitySummaryReportData,
|
||||
SalesTaxLiabilitySummaryTotal,
|
||||
} from './SalesTaxLiability.types';
|
||||
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||
import { ModelObject } from 'objection';
|
||||
import { TaxRateModel } from '@/modules/TaxRates/models/TaxRate.model';
|
||||
import { SalesTaxLiabilitySummaryRepository } from './SalesTaxLiabilitySummaryRepository';
|
||||
|
||||
export class SalesTaxLiabilitySummary extends FinancialSheet {
|
||||
private query: SalesTaxLiabilitySummaryQuery;
|
||||
private repository: SalesTaxLiabilitySummaryRepository;
|
||||
|
||||
/**
|
||||
* Sales tax liability summary constructor.
|
||||
* @param {SalesTaxLiabilitySummaryQuery} query
|
||||
* @param {ITaxRate[]} taxRates
|
||||
* @param {SalesTaxLiabilitySummaryPayableById} payableTaxesById
|
||||
* @param {SalesTaxLiabilitySummarySalesById} salesTaxesById
|
||||
*/
|
||||
constructor(
|
||||
query: SalesTaxLiabilitySummaryQuery,
|
||||
repository: SalesTaxLiabilitySummaryRepository,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.query = query;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the tax rate liability node.
|
||||
* @param {ITaxRate} taxRate
|
||||
* @returns {SalesTaxLiabilitySummaryRate}
|
||||
*/
|
||||
private taxRateLiability = (
|
||||
taxRate: ModelObject<TaxRateModel>,
|
||||
): SalesTaxLiabilitySummaryRate => {
|
||||
const payableTax = this.repository.taxesPayableByTaxRateId[taxRate.id];
|
||||
const salesTax = this.repository.accountTransactionsByTaxRateId[taxRate.id];
|
||||
|
||||
const payableTaxAmount = payableTax
|
||||
? payableTax.credit - payableTax.debit
|
||||
: 0;
|
||||
const salesTaxAmount = salesTax ? salesTax.credit - salesTax.debit : 0;
|
||||
|
||||
// Calculates the tax percentage.
|
||||
const taxPercentage = R.compose(
|
||||
R.unless(R.equals(0), R.divide(R.__, salesTaxAmount)),
|
||||
)(payableTaxAmount);
|
||||
|
||||
// Calculates the payable tax amount.
|
||||
const collectedTaxAmount = payableTax ? payableTax.debit : 0;
|
||||
|
||||
return {
|
||||
id: taxRate.id,
|
||||
taxName: `${taxRate.name} (${taxRate.rate}%)`,
|
||||
taxableAmount: this.getAmountMeta(salesTaxAmount),
|
||||
taxAmount: this.getAmountMeta(payableTaxAmount),
|
||||
taxPercentage: this.getPercentageTotalAmountMeta(taxPercentage),
|
||||
collectedTaxAmount: this.getAmountMeta(collectedTaxAmount),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters the non-transactions tax rates.
|
||||
* @param {SalesTaxLiabilitySummaryRate[]} nodes
|
||||
* @returns {SalesTaxLiabilitySummaryRate[]}
|
||||
*/
|
||||
private filterNonTransactionsTaxRates = (
|
||||
nodes: SalesTaxLiabilitySummaryRate[],
|
||||
): SalesTaxLiabilitySummaryRate[] => {
|
||||
return nodes.filter((node) => {
|
||||
const salesTrxs = this.repository.accountTransactionsByTaxRateId[node.id];
|
||||
const payableTrxs = this.repository.taxesPayableByTaxRateId[node.id];
|
||||
|
||||
return !isEmpty(salesTrxs) || !isEmpty(payableTrxs);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the tax rates liability nodes.
|
||||
* @returns {SalesTaxLiabilitySummaryRate[]}
|
||||
*/
|
||||
private taxRatesLiability = (): SalesTaxLiabilitySummaryRate[] => {
|
||||
return R.compose(
|
||||
this.filterNonTransactionsTaxRates,
|
||||
R.map(this.taxRateLiability),
|
||||
)(this.repository.taxRates);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the tax rates total node.
|
||||
* @param {SalesTaxLiabilitySummaryRate[]} nodes
|
||||
* @returns {SalesTaxLiabilitySummaryTotal}
|
||||
*/
|
||||
private taxRatesTotal = (
|
||||
nodes: SalesTaxLiabilitySummaryRate[],
|
||||
): SalesTaxLiabilitySummaryTotal => {
|
||||
const taxableAmount = sumBy(nodes, 'taxableAmount.amount');
|
||||
const taxAmount = sumBy(nodes, 'taxAmount.amount');
|
||||
const collectedTaxAmount = sumBy(nodes, 'collectedTaxAmount.amount');
|
||||
|
||||
return {
|
||||
taxableAmount: this.getTotalAmountMeta(taxableAmount),
|
||||
taxAmount: this.getTotalAmountMeta(taxAmount),
|
||||
collectedTaxAmount: this.getTotalAmountMeta(collectedTaxAmount),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the report data.
|
||||
* @returns {SalesTaxLiabilitySummaryReportData}
|
||||
*/
|
||||
public reportData = (): SalesTaxLiabilitySummaryReportData => {
|
||||
const taxRates = this.taxRatesLiability();
|
||||
const total = this.taxRatesTotal(taxRates);
|
||||
|
||||
return { taxRates, total };
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
import { SalesTaxLiabilitySummaryTableInjectable } from './SalesTaxLiabilitySummaryTableInjectable';
|
||||
import { SalesTaxLiabilitySummaryExportInjectable } from './SalesTaxLiabilitySummaryExportInjectable';
|
||||
import { SalesTaxLiabilitySummaryService } from './SalesTaxLiabilitySummaryService';
|
||||
import { SalesTaxLiabiltiySummaryPdf } from './SalesTaxLiabiltiySummaryPdf';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { SalesTaxLiabilitySummaryQuery } from './SalesTaxLiability.types';
|
||||
|
||||
@Injectable()
|
||||
export class SalesTaxLiabilitySummaryApplication {
|
||||
constructor(
|
||||
private readonly salesTaxLiabilitySheet: SalesTaxLiabilitySummaryService,
|
||||
private readonly salesTaxLiabilityExport: SalesTaxLiabilitySummaryExportInjectable,
|
||||
private readonly salesTaxLiabilityTable: SalesTaxLiabilitySummaryTableInjectable,
|
||||
private readonly salesTaxLiabiltiyPdf: SalesTaxLiabiltiySummaryPdf,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the sales tax liability summary in json format.
|
||||
* @param {SalesTaxLiabilitySummaryQuery} query -
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public sheet(query: SalesTaxLiabilitySummaryQuery) {
|
||||
return this.salesTaxLiabilitySheet.salesTaxLiability(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sales tax liability summary in table format.
|
||||
* @param {SalesTaxLiabilitySummaryQuery} query
|
||||
* @return {Promise<Buffer>}
|
||||
*/
|
||||
public table(query: SalesTaxLiabilitySummaryQuery) {
|
||||
return this.salesTaxLiabilityTable.table(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sales tax liability summary in XLSX format.
|
||||
* @param {SalesTaxLiabilitySummaryQuery} query
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public xlsx(query: SalesTaxLiabilitySummaryQuery): Promise<Buffer> {
|
||||
return this.salesTaxLiabilityExport.xlsx(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sales tax liability summary in CSV format.
|
||||
* @param {SalesTaxLiabilitySummaryQuery} query
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
public csv(query: SalesTaxLiabilitySummaryQuery): Promise<string> {
|
||||
return this.salesTaxLiabilityExport.csv(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sales tax liability summary in PDF format.
|
||||
* @param {SalesTaxLiabilitySummaryQuery} query
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public pdf(query: SalesTaxLiabilitySummaryQuery): Promise<Buffer> {
|
||||
return this.salesTaxLiabiltiyPdf.pdf(query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { TableSheet } from '../../common/TableSheet';
|
||||
import { SalesTaxLiabilitySummaryQuery } from './SalesTaxLiability.types';
|
||||
import { SalesTaxLiabilitySummaryTableInjectable } from './SalesTaxLiabilitySummaryTableInjectable';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class SalesTaxLiabilitySummaryExportInjectable {
|
||||
constructor(
|
||||
private readonly salesTaxLiabilityTable: SalesTaxLiabilitySummaryTableInjectable,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the cashflow sheet in XLSX format.
|
||||
* @param {SalesTaxLiabilitySummaryQuery} query
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public async xlsx(query: SalesTaxLiabilitySummaryQuery): Promise<Buffer> {
|
||||
const table = await this.salesTaxLiabilityTable.table(query);
|
||||
|
||||
const tableSheet = new TableSheet(table.table);
|
||||
const tableCsv = tableSheet.convertToXLSX();
|
||||
|
||||
return tableSheet.convertToBuffer(tableCsv, 'xlsx');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cashflow sheet in CSV format.
|
||||
* @param {SalesTaxLiabilitySummaryQuery} query
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
public async csv(query: SalesTaxLiabilitySummaryQuery): Promise<string> {
|
||||
const table = await this.salesTaxLiabilityTable.table(query);
|
||||
|
||||
const tableSheet = new TableSheet(table.table);
|
||||
const tableCsv = tableSheet.convertToCSV();
|
||||
|
||||
return tableCsv;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as moment from 'moment';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { FinancialSheetMeta } from '../../common/FinancialSheetMeta';
|
||||
import { SalesTaxLiabilitySummaryQuery } from './SalesTaxLiability.types';
|
||||
|
||||
@Injectable()
|
||||
export class SalesTaxLiabilitySummaryMeta {
|
||||
constructor(private readonly financialSheetMeta: FinancialSheetMeta) {}
|
||||
|
||||
/**
|
||||
* Retrieves the report meta.
|
||||
* @param {number} tenantId
|
||||
* @param {SalesTaxLiabilitySummaryQuery} filter
|
||||
*/
|
||||
public async meta(query: SalesTaxLiabilitySummaryQuery) {
|
||||
const commonMeta = await this.financialSheetMeta.meta();
|
||||
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD');
|
||||
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD');
|
||||
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`;
|
||||
|
||||
const sheetName = 'Sales Tax Liability Summary';
|
||||
|
||||
return {
|
||||
...commonMeta,
|
||||
sheetName,
|
||||
formattedFromDate,
|
||||
formattedToDate,
|
||||
formattedDateRange,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import { ACCOUNT_TYPE } from '@/constants/accounts';
|
||||
import {
|
||||
SalesTaxLiabilitySummaryPayableById,
|
||||
SalesTaxLiabilitySummarySalesById,
|
||||
} from './SalesTaxLiability.types';
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import { keyBy } from 'lodash';
|
||||
import { TaxRateModel } from '@/modules/TaxRates/models/TaxRate.model';
|
||||
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { ModelObject } from 'objection';
|
||||
|
||||
@Injectable({ scope: Scope.TRANSIENT })
|
||||
export class SalesTaxLiabilitySummaryRepository {
|
||||
@Inject(TaxRateModel.name)
|
||||
private readonly taxRateModel: typeof TaxRateModel;
|
||||
|
||||
@Inject(AccountTransaction.name)
|
||||
private readonly accountTransactionModel: typeof AccountTransaction;
|
||||
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: typeof Account;
|
||||
|
||||
/**
|
||||
* @param {SalesTaxLiabilitySummarySalesById}
|
||||
*/
|
||||
accountTransactionsByTaxRateId: SalesTaxLiabilitySummarySalesById;
|
||||
|
||||
/**
|
||||
* @param {SalesTaxLiabilitySummaryPayableById}
|
||||
*/
|
||||
taxesPayableByTaxRateId: SalesTaxLiabilitySummaryPayableById;
|
||||
|
||||
/**
|
||||
* @param {Array<ModelObject<TaxRateModel>>}
|
||||
*/
|
||||
taxRates: Array<ModelObject<TaxRateModel>>;
|
||||
|
||||
/**
|
||||
* Load data.
|
||||
*/
|
||||
async load() {
|
||||
await this.initTaxRates();
|
||||
await this.initTaxesPayableByTaxRateId();
|
||||
await this.initAccountTransactionsByTaxRateId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize tax rates.
|
||||
*/
|
||||
async initTaxRates() {
|
||||
const taxRates = await this.getTaxRates();
|
||||
this.taxRates = taxRates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize account transactions by tax rate id.
|
||||
*/
|
||||
async initAccountTransactionsByTaxRateId() {
|
||||
const transactionsByTaxRateId = await this.taxesSalesSumGroupedByRateId();
|
||||
|
||||
this.accountTransactionsByTaxRateId = transactionsByTaxRateId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize taxes payable by tax rate id.
|
||||
*/
|
||||
async initTaxesPayableByTaxRateId() {
|
||||
const payableTaxes = await this.getTaxesPayableSumGroupedByRateId();
|
||||
|
||||
this.taxesPayableByTaxRateId = payableTaxes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve tax rates.
|
||||
* @param {number} tenantId
|
||||
* @returns {Promise<TaxRate[]>}
|
||||
*/
|
||||
public getTaxRates = () => {
|
||||
return this.taxRateModel.query().orderBy('name', 'desc');
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve taxes payable sum grouped by tax rate id.
|
||||
* @returns {Promise<SalesTaxLiabilitySummaryPayableById>}
|
||||
*/
|
||||
public async getTaxesPayableSumGroupedByRateId(): Promise<SalesTaxLiabilitySummaryPayableById> {
|
||||
// Retrieves tax payable accounts.
|
||||
const taxPayableAccounts = await this.accountModel
|
||||
.query()
|
||||
.whereIn('accountType', [ACCOUNT_TYPE.TAX_PAYABLE]);
|
||||
|
||||
const payableAccountsIds = taxPayableAccounts.map((account) => account.id);
|
||||
|
||||
const groupedTaxesById = await this.accountTransactionModel
|
||||
.query()
|
||||
.whereIn('account_id', payableAccountsIds)
|
||||
.whereNot('tax_rate_id', null)
|
||||
.groupBy('tax_rate_id')
|
||||
.select(['tax_rate_id'])
|
||||
.sum('credit as credit')
|
||||
.sum('debit as debit');
|
||||
|
||||
return keyBy(groupedTaxesById, 'taxRateId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve taxes sales sum grouped by tax rate id.
|
||||
* @returns {Promise<SalesTaxLiabilitySummarySalesById>}
|
||||
*/
|
||||
public taxesSalesSumGroupedByRateId =
|
||||
async (): Promise<SalesTaxLiabilitySummarySalesById> => {
|
||||
const incomeAccounts = await this.accountModel
|
||||
.query()
|
||||
.whereIn('accountType', [
|
||||
ACCOUNT_TYPE.INCOME,
|
||||
ACCOUNT_TYPE.OTHER_INCOME,
|
||||
]);
|
||||
const incomeAccountsIds = incomeAccounts.map((account) => account.id);
|
||||
|
||||
const groupedTaxesById = await this.accountTransactionModel
|
||||
.query()
|
||||
.whereIn('account_id', incomeAccountsIds)
|
||||
.whereNot('tax_rate_id', null)
|
||||
.groupBy('tax_rate_id')
|
||||
.select(['tax_rate_id'])
|
||||
.sum('credit as credit')
|
||||
.sum('debit as debit');
|
||||
|
||||
return keyBy(groupedTaxesById, 'taxRateId');
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { SalesTaxLiabilitySummaryRepository } from './SalesTaxLiabilitySummaryRepository';
|
||||
import { SalesTaxLiabilitySummary } from './SalesTaxLiabilitySummary';
|
||||
import { SalesTaxLiabilitySummaryMeta } from './SalesTaxLiabilitySummaryMeta';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { SalesTaxLiabilitySummaryQuery } from './SalesTaxLiability.types';
|
||||
|
||||
@Injectable()
|
||||
export class SalesTaxLiabilitySummaryService {
|
||||
constructor(
|
||||
private readonly repository: SalesTaxLiabilitySummaryRepository,
|
||||
private readonly salesTaxLiabilityMeta: SalesTaxLiabilitySummaryMeta,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve sales tax liability summary.
|
||||
* @param {SalesTaxLiabilitySummaryQuery} query
|
||||
* @returns
|
||||
*/
|
||||
public async salesTaxLiability(query: SalesTaxLiabilitySummaryQuery) {
|
||||
await this.repository.load();
|
||||
|
||||
const taxLiabilitySummary = new SalesTaxLiabilitySummary(
|
||||
query,
|
||||
this.repository,
|
||||
);
|
||||
const meta = await this.salesTaxLiabilityMeta.meta(query);
|
||||
|
||||
return {
|
||||
data: taxLiabilitySummary.reportData(),
|
||||
query,
|
||||
meta,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
SalesTaxLiabilitySummaryQuery,
|
||||
SalesTaxLiabilitySummaryRate,
|
||||
SalesTaxLiabilitySummaryReportData,
|
||||
SalesTaxLiabilitySummaryTotal,
|
||||
} from './SalesTaxLiability.types';
|
||||
import { AgingReport } from '../AgingSummary/AgingReport';
|
||||
import { IROW_TYPE } from './_constants';
|
||||
import { FinancialTable } from '../../common/FinancialTable';
|
||||
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
|
||||
import { ITableRow } from '../../types/Table.types';
|
||||
import { ITableColumn } from '../../types/Table.types';
|
||||
import { tableRowMapper } from '../../utils/Table.utils';
|
||||
|
||||
export class SalesTaxLiabilitySummaryTable extends R.pipe(
|
||||
FinancialTable,
|
||||
FinancialSheetStructure,
|
||||
)(AgingReport) {
|
||||
private data: SalesTaxLiabilitySummaryReportData;
|
||||
private query: SalesTaxLiabilitySummaryQuery;
|
||||
|
||||
/**
|
||||
* Sales tax liability summary table constructor.
|
||||
* @param {SalesTaxLiabilitySummaryReportData} data
|
||||
* @param {SalesTaxLiabilitySummaryQuery} query
|
||||
*/
|
||||
constructor(
|
||||
data: SalesTaxLiabilitySummaryReportData,
|
||||
query: SalesTaxLiabilitySummaryQuery,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.data = data;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the tax rate row accessors.
|
||||
* @returns {ITableColumnAccessor[]}
|
||||
*/
|
||||
private get taxRateRowAccessor() {
|
||||
return [
|
||||
{ key: 'taxName', accessor: 'taxName' },
|
||||
{ key: 'taxPercentage', accessor: 'taxPercentage.formattedAmount' },
|
||||
{ key: 'taxableAmount', accessor: 'taxableAmount.formattedAmount' },
|
||||
{ key: 'collectedTax', accessor: 'collectedTaxAmount.formattedAmount' },
|
||||
{ key: 'taxAmount', accessor: 'taxAmount.formattedAmount' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the tax rate total row accessors.
|
||||
* @returns {ITableColumnAccessor[]}
|
||||
*/
|
||||
private get taxRateTotalRowAccessors() {
|
||||
return [
|
||||
{ key: 'taxName', value: 'Total' },
|
||||
{ key: 'taxPercentage', value: '' },
|
||||
{ key: 'taxableAmount', accessor: 'taxableAmount.formattedAmount' },
|
||||
{ key: 'collectedTax', accessor: 'collectedTaxAmount.formattedAmount' },
|
||||
{ key: 'taxAmount', accessor: 'taxAmount.formattedAmount' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the tax rate node to table row.
|
||||
* @param {SalesTaxLiabilitySummaryRate} node
|
||||
* @returns {ITableRow}
|
||||
*/
|
||||
private taxRateTableRowMapper = (
|
||||
node: SalesTaxLiabilitySummaryRate,
|
||||
): ITableRow => {
|
||||
const columns = this.taxRateRowAccessor;
|
||||
const meta = {
|
||||
rowTypes: [IROW_TYPE.TaxRate],
|
||||
id: node.id,
|
||||
};
|
||||
return tableRowMapper(node, columns, meta);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps the tax rates nodes to table rows.
|
||||
* @param {SalesTaxLiabilitySummaryRate[]} nodes
|
||||
* @returns {ITableRow[]}
|
||||
*/
|
||||
private taxRatesTableRowsMapper = (
|
||||
nodes: SalesTaxLiabilitySummaryRate[],
|
||||
): ITableRow[] => {
|
||||
return nodes.map(this.taxRateTableRowMapper);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps the tax rate total node to table row.
|
||||
* @param {SalesTaxLiabilitySummaryTotal} node
|
||||
* @returns {ITableRow}
|
||||
*/
|
||||
private taxRateTotalRowMapper = (node: SalesTaxLiabilitySummaryTotal) => {
|
||||
const columns = this.taxRateTotalRowAccessors;
|
||||
const meta = {
|
||||
rowTypes: [IROW_TYPE.Total],
|
||||
};
|
||||
return tableRowMapper(node, columns, meta);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the tax rate total row.
|
||||
* @returns {ITableRow}
|
||||
*/
|
||||
private get taxRateTotalRow(): ITableRow {
|
||||
return this.taxRateTotalRowMapper(this.data.total);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the tax rates rows.
|
||||
* @returns {ITableRow[]}
|
||||
*/
|
||||
private get taxRatesRows(): ITableRow[] {
|
||||
return this.taxRatesTableRowsMapper(this.data.taxRates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the table rows.
|
||||
* @returns {ITableRow[]}
|
||||
*/
|
||||
public tableRows(): ITableRow[] {
|
||||
return R.compose(
|
||||
R.unless(R.isEmpty, R.append(this.taxRateTotalRow)),
|
||||
R.concat(this.taxRatesRows),
|
||||
)([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the table columns.
|
||||
* @returns {ITableColumn[]}
|
||||
*/
|
||||
public tableColumns(): ITableColumn[] {
|
||||
return R.compose(this.tableColumnsCellIndexing)([
|
||||
{
|
||||
label: 'Tax Name',
|
||||
key: 'taxName',
|
||||
},
|
||||
{
|
||||
label: 'Tax Percentage',
|
||||
key: 'taxPercentage',
|
||||
},
|
||||
{
|
||||
label: 'Taxable Amount',
|
||||
key: 'taxableAmount',
|
||||
},
|
||||
{
|
||||
label: 'Collected Tax',
|
||||
key: 'collectedTax',
|
||||
},
|
||||
{
|
||||
label: 'Tax Amount',
|
||||
key: 'taxRate',
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
ISalesTaxLiabilitySummaryTable,
|
||||
SalesTaxLiabilitySummaryQuery,
|
||||
} from './SalesTaxLiability.types';
|
||||
import { SalesTaxLiabilitySummaryTable } from './SalesTaxLiabilitySummaryTable';
|
||||
import { SalesTaxLiabilitySummaryService } from './SalesTaxLiabilitySummaryService';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class SalesTaxLiabilitySummaryTableInjectable {
|
||||
constructor(
|
||||
private readonly salesTaxLiability: SalesTaxLiabilitySummaryService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve sales tax liability summary table.
|
||||
* @param {SalesTaxLiabilitySummaryQuery} query
|
||||
* @returns {Promise<ISalesTaxLiabilitySummaryTable>}
|
||||
*/
|
||||
public async table(
|
||||
query: SalesTaxLiabilitySummaryQuery,
|
||||
): Promise<ISalesTaxLiabilitySummaryTable> {
|
||||
const report = await this.salesTaxLiability.salesTaxLiability(query);
|
||||
// Creates the sales tax liability summary table.
|
||||
const table = new SalesTaxLiabilitySummaryTable(report.data, query);
|
||||
|
||||
return {
|
||||
table: {
|
||||
rows: table.tableRows(),
|
||||
columns: table.tableColumns(),
|
||||
},
|
||||
query: report.query,
|
||||
meta: report.meta,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { SalesTaxLiabilitySummaryTableInjectable } from './SalesTaxLiabilitySummaryTableInjectable';
|
||||
import { TableSheetPdf } from '../../common/TableSheetPdf';
|
||||
import { SalesTaxLiabilitySummaryQuery } from './SalesTaxLiability.types';
|
||||
|
||||
@Injectable()
|
||||
export class SalesTaxLiabiltiySummaryPdf {
|
||||
constructor(
|
||||
private readonly salesTaxLiabiltiySummaryTable: SalesTaxLiabilitySummaryTableInjectable,
|
||||
private readonly tableSheetPdf: TableSheetPdf,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Converts the given sales tax liability summary table to pdf.
|
||||
* @param {ISalesByItemsReportQuery} query - Balance sheet query.
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public async pdf(query: SalesTaxLiabilitySummaryQuery): Promise<Buffer> {
|
||||
const table = await this.salesTaxLiabiltiySummaryTable.table(
|
||||
query,
|
||||
);
|
||||
return this.tableSheetPdf.convertToPdf(
|
||||
table.table,
|
||||
table.meta.sheetName,
|
||||
table.meta.formattedDateRange,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum IROW_TYPE {
|
||||
TaxRate = 'TaxRate',
|
||||
Total = 'Total',
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import { TransactionsByContactRepository } from './TransactionsByContactReposito
|
||||
|
||||
export class TransactionsByContact extends FinancialSheet {
|
||||
public readonly filter: ITransactionsByContactsFilter;
|
||||
public readonly i18n: I18nService
|
||||
public readonly i18n: I18nService;
|
||||
public readonly repository: TransactionsByContactRepository;
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,7 @@ export class TransactionsByContact extends FinancialSheet {
|
||||
* @return {Omit<ITransactionsByContactsTransaction, 'runningBalance'>}
|
||||
*/
|
||||
protected contactTransactionMapper(
|
||||
entry: ILedgerEntry
|
||||
entry: ILedgerEntry,
|
||||
): Omit<ITransactionsByContactsTransaction, 'runningBalance'> {
|
||||
const account = this.repository.accountsGraph.getNodeData(entry.accountId);
|
||||
const currencyCode = this.baseCurrency;
|
||||
@@ -37,6 +37,7 @@ export class TransactionsByContact extends FinancialSheet {
|
||||
// @ts-ignore
|
||||
// transactionType: this.i18n.t(entry.referenceTypeFormatted),
|
||||
transactionType: '',
|
||||
// @ts-ignore
|
||||
date: entry.date,
|
||||
createdAt: entry.createdAt,
|
||||
};
|
||||
@@ -51,7 +52,7 @@ export class TransactionsByContact extends FinancialSheet {
|
||||
protected contactTransactionRunningBalance(
|
||||
openingBalance: number,
|
||||
accountNormal: 'credit' | 'debit',
|
||||
transactions: Omit<ITransactionsByContactsTransaction, 'runningBalance'>[]
|
||||
transactions: Omit<ITransactionsByContactsTransaction, 'runningBalance'>[],
|
||||
): any {
|
||||
let _openingBalance = openingBalance;
|
||||
|
||||
@@ -69,10 +70,10 @@ export class TransactionsByContact extends FinancialSheet {
|
||||
|
||||
const runningBalance = this.getTotalAmountMeta(
|
||||
_openingBalance,
|
||||
transaction.currencyCode
|
||||
transaction.currencyCode,
|
||||
);
|
||||
return { ...transaction, runningBalance };
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -85,7 +86,7 @@ export class TransactionsByContact extends FinancialSheet {
|
||||
protected getContactClosingBalance(
|
||||
customerTransactions: ITransactionsByContactsTransaction[],
|
||||
contactNormal: 'credit' | 'debit',
|
||||
openingBalance: number
|
||||
openingBalance: number,
|
||||
): number {
|
||||
const closingBalance = openingBalance;
|
||||
|
||||
@@ -124,7 +125,7 @@ export class TransactionsByContact extends FinancialSheet {
|
||||
*/
|
||||
protected getContactAmount(
|
||||
amount: number,
|
||||
currencyCode: string
|
||||
currencyCode: string,
|
||||
): ITransactionsByContactsAmount {
|
||||
return {
|
||||
amount,
|
||||
@@ -153,7 +154,7 @@ export class TransactionsByContact extends FinancialSheet {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private filterContactByNoneTransaction = (
|
||||
transactionsByContact: ITransactionsByContactsContact
|
||||
transactionsByContact: ITransactionsByContactsContact,
|
||||
): boolean => {
|
||||
return transactionsByContact.transactions.length > 0;
|
||||
};
|
||||
@@ -164,7 +165,7 @@ export class TransactionsByContact extends FinancialSheet {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private filterContactNoneZero = (
|
||||
transactionsByContact: ITransactionsByContactsContact
|
||||
transactionsByContact: ITransactionsByContactsContact,
|
||||
): boolean => {
|
||||
return transactionsByContact.closingBalance.amount !== 0;
|
||||
};
|
||||
@@ -190,7 +191,7 @@ export class TransactionsByContact extends FinancialSheet {
|
||||
* @returns {ICustomerBalanceSummaryCustomer[]}
|
||||
*/
|
||||
protected contactsFilter = (
|
||||
nodes: ITransactionsByContactsContact[]
|
||||
nodes: ITransactionsByContactsContact[],
|
||||
): ITransactionsByContactsContact[] => {
|
||||
return nodes.filter(this.contactNodeFilter);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user