mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
refactor: financial statements to nestjs
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { Model, raw } from 'objection';
|
import { Model, raw } from 'objection';
|
||||||
import moment, { unitOfTime } from 'moment';
|
import * as moment from 'moment';
|
||||||
|
import { unitOfTime } from 'moment';
|
||||||
import { isEmpty, castArray } from 'lodash';
|
import { isEmpty, castArray } from 'lodash';
|
||||||
import { BaseModel } from '@/models/Model';
|
import { BaseModel } from '@/models/Model';
|
||||||
import { Account } from './Account.model';
|
import { Account } from './Account.model';
|
||||||
@@ -10,6 +11,7 @@ export class AccountTransaction extends BaseModel {
|
|||||||
public readonly referenceId: number;
|
public readonly referenceId: number;
|
||||||
public readonly accountId: number;
|
public readonly accountId: number;
|
||||||
public readonly contactId: number;
|
public readonly contactId: number;
|
||||||
|
public readonly contactType: string;
|
||||||
public readonly credit: number;
|
public readonly credit: number;
|
||||||
public readonly debit: number;
|
public readonly debit: number;
|
||||||
public readonly exchangeRate: number;
|
public readonly exchangeRate: number;
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import { PurchasesByItemsModule } from './modules/PurchasesByItems/PurchasesByIt
|
|||||||
import { CustomerBalanceSummaryModule } from './modules/CustomerBalanceSummary/CustomerBalanceSummary.module';
|
import { CustomerBalanceSummaryModule } from './modules/CustomerBalanceSummary/CustomerBalanceSummary.module';
|
||||||
import { SalesByItemsModule } from './modules/SalesByItems/SalesByItems.module';
|
import { SalesByItemsModule } from './modules/SalesByItems/SalesByItems.module';
|
||||||
import { GeneralLedgerModule } from './modules/GeneralLedger/GeneralLedger.module';
|
import { GeneralLedgerModule } from './modules/GeneralLedger/GeneralLedger.module';
|
||||||
|
import { TransactionsByCustomerModule } from './modules/TransactionsByCustomer/TransactionsByCustomer.module';
|
||||||
|
import { TrialBalanceSheetModule } from './modules/TrialBalanceSheet/TrialBalanceSheet.module';
|
||||||
|
import { TransactionsByReferenceModule } from './modules/TransactionsByReference/TransactionByReference.module';
|
||||||
|
import { TransactionsByVendorModule } from './modules/TransactionsByVendor/TransactionsByVendor.module';
|
||||||
//
|
//
|
||||||
@Module({
|
@Module({
|
||||||
providers: [],
|
providers: [],
|
||||||
@@ -10,7 +14,14 @@ import { GeneralLedgerModule } from './modules/GeneralLedger/GeneralLedger.modul
|
|||||||
PurchasesByItemsModule,
|
PurchasesByItemsModule,
|
||||||
CustomerBalanceSummaryModule,
|
CustomerBalanceSummaryModule,
|
||||||
SalesByItemsModule,
|
SalesByItemsModule,
|
||||||
GeneralLedgerModule
|
GeneralLedgerModule,
|
||||||
|
TrialBalanceSheetModule,
|
||||||
|
TransactionsByCustomerModule,
|
||||||
|
TransactionsByVendorModule,
|
||||||
|
// TransactionsByReferenceModule,
|
||||||
|
// TransactionsByVendorModule,
|
||||||
|
// TransactionsByContactModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class FinancialStatementsModule {}
|
export class FinancialStatementsModule {}
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export class FinancialSheet {
|
|||||||
* @param {string} format
|
* @param {string} format
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
protected getDateMeta(date: Date, format = 'YYYY-MM-DD') {
|
protected getDateMeta(date: moment.MomentInput, format = 'YYYY-MM-DD') {
|
||||||
return {
|
return {
|
||||||
formattedDate: moment(date).format(format),
|
formattedDate: moment(date).format(format),
|
||||||
date: moment(date).toDate(),
|
date: moment(date).toDate(),
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
import { sumBy, defaultTo } from 'lodash';
|
||||||
|
import {
|
||||||
|
ITransactionsByContactsTransaction,
|
||||||
|
ITransactionsByContactsAmount,
|
||||||
|
ITransactionsByContactsFilter,
|
||||||
|
ITransactionsByContactsContact,
|
||||||
|
} from './TransactionsByContact.types';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { allPassedConditionsPass } from '@/utils/all-conditions-passed';
|
||||||
|
import { TransactionsByContactRepository } from './TransactionsByContactRepository';
|
||||||
|
|
||||||
|
export class TransactionsByContact extends FinancialSheet {
|
||||||
|
public readonly filter: ITransactionsByContactsFilter;
|
||||||
|
public readonly i18n: I18nService
|
||||||
|
public readonly repository: TransactionsByContactRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer transaction mapper.
|
||||||
|
* @param {ILedgerEntry} entry - Ledger entry.
|
||||||
|
* @return {Omit<ITransactionsByContactsTransaction, 'runningBalance'>}
|
||||||
|
*/
|
||||||
|
protected contactTransactionMapper(
|
||||||
|
entry: ILedgerEntry
|
||||||
|
): Omit<ITransactionsByContactsTransaction, 'runningBalance'> {
|
||||||
|
const account = this.repository.accountsGraph.getNodeData(entry.accountId);
|
||||||
|
const currencyCode = this.baseCurrency;
|
||||||
|
|
||||||
|
return {
|
||||||
|
credit: this.getContactAmount(entry.credit, currencyCode),
|
||||||
|
debit: this.getContactAmount(entry.debit, currencyCode),
|
||||||
|
accountName: account.name,
|
||||||
|
currencyCode: this.baseCurrency,
|
||||||
|
transactionNumber: entry.transactionNumber,
|
||||||
|
transactionType: this.i18n.t(entry.referenceTypeFormatted),
|
||||||
|
date: entry.date,
|
||||||
|
createdAt: entry.createdAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer transactions mapper with running balance.
|
||||||
|
* @param {number} openingBalance
|
||||||
|
* @param {ITransactionsByContactsTransaction[]} transactions
|
||||||
|
* @returns {ITransactionsByContactsTransaction[]}
|
||||||
|
*/
|
||||||
|
protected contactTransactionRunningBalance(
|
||||||
|
openingBalance: number,
|
||||||
|
accountNormal: 'credit' | 'debit',
|
||||||
|
transactions: Omit<ITransactionsByContactsTransaction, 'runningBalance'>[]
|
||||||
|
): any {
|
||||||
|
let _openingBalance = openingBalance;
|
||||||
|
|
||||||
|
return transactions.map(
|
||||||
|
(transaction: ITransactionsByContactsTransaction) => {
|
||||||
|
_openingBalance +=
|
||||||
|
accountNormal === 'debit'
|
||||||
|
? transaction.debit.amount
|
||||||
|
: -1 * transaction.debit.amount;
|
||||||
|
|
||||||
|
_openingBalance +=
|
||||||
|
accountNormal === 'credit'
|
||||||
|
? transaction.credit.amount
|
||||||
|
: -1 * transaction.credit.amount;
|
||||||
|
|
||||||
|
const runningBalance = this.getTotalAmountMeta(
|
||||||
|
_openingBalance,
|
||||||
|
transaction.currencyCode
|
||||||
|
);
|
||||||
|
return { ...transaction, runningBalance };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the customer closing balance from the given transactions and opening balance.
|
||||||
|
* @param {number} customerTransactions
|
||||||
|
* @param {number} openingBalance
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
protected getContactClosingBalance(
|
||||||
|
customerTransactions: ITransactionsByContactsTransaction[],
|
||||||
|
contactNormal: 'credit' | 'debit',
|
||||||
|
openingBalance: number
|
||||||
|
): number {
|
||||||
|
const closingBalance = openingBalance;
|
||||||
|
|
||||||
|
const totalCredit = sumBy(customerTransactions, 'credit.amount');
|
||||||
|
const totalDebit = sumBy(customerTransactions, 'debit.amount');
|
||||||
|
|
||||||
|
const total =
|
||||||
|
contactNormal === 'debit'
|
||||||
|
? totalDebit - totalCredit
|
||||||
|
: totalCredit - totalDebit;
|
||||||
|
|
||||||
|
return closingBalance + total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the given customer opening balance from the given customer id.
|
||||||
|
* @param {number} customerId
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
protected getContactOpeningBalance(customerId: number): number {
|
||||||
|
const openingBalanceLedger = this.repository.ledger
|
||||||
|
.whereContactId(customerId)
|
||||||
|
.whereToDate(this.filter.fromDate);
|
||||||
|
|
||||||
|
// Retrieve the closing balance of the ledger.
|
||||||
|
const openingBalance = openingBalanceLedger.getClosingBalance();
|
||||||
|
|
||||||
|
return defaultTo(openingBalance, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the customer amount format meta.
|
||||||
|
* @param {number} amount
|
||||||
|
* @param {string} currencyCode
|
||||||
|
* @returns {ITransactionsByContactsAmount}
|
||||||
|
*/
|
||||||
|
protected getContactAmount(
|
||||||
|
amount: number,
|
||||||
|
currencyCode: string
|
||||||
|
): ITransactionsByContactsAmount {
|
||||||
|
return {
|
||||||
|
amount,
|
||||||
|
formattedAmount: this.formatNumber(amount, { currencyCode }),
|
||||||
|
currencyCode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the contact total amount format meta.
|
||||||
|
* @param {number} amount - Amount.
|
||||||
|
* @param {string} currencyCode - Currency code./
|
||||||
|
* @returns {ITransactionsByContactsAmount}
|
||||||
|
*/
|
||||||
|
protected getTotalAmountMeta(amount: number, currencyCode: string) {
|
||||||
|
return {
|
||||||
|
amount,
|
||||||
|
formattedAmount: this.formatTotalNumber(amount, { currencyCode }),
|
||||||
|
currencyCode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter customer section that has no transactions.
|
||||||
|
* @param {ITransactionsByCustomersCustomer} transactionsByCustomer
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
private filterContactByNoneTransaction = (
|
||||||
|
transactionsByContact: ITransactionsByContactsContact
|
||||||
|
): boolean => {
|
||||||
|
return transactionsByContact.transactions.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters customer section has zero closing balnace.
|
||||||
|
* @param {ITransactionsByCustomersCustomer} transactionsByCustomer
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
private filterContactNoneZero = (
|
||||||
|
transactionsByContact: ITransactionsByContactsContact
|
||||||
|
): boolean => {
|
||||||
|
return transactionsByContact.closingBalance.amount !== 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the given customer node;
|
||||||
|
* @param {ITransactionsByContactsContact} node - Contact node.
|
||||||
|
*/
|
||||||
|
private contactNodeFilter = (node: ITransactionsByContactsContact) => {
|
||||||
|
const { noneTransactions, noneZero } = this.filter;
|
||||||
|
|
||||||
|
// Conditions pair filter detarminer.
|
||||||
|
const condsPairFilters = [
|
||||||
|
[noneTransactions, this.filterContactByNoneTransaction],
|
||||||
|
[noneZero, this.filterContactNoneZero],
|
||||||
|
];
|
||||||
|
return allPassedConditionsPass(condsPairFilters)(node);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the given customers nodes.
|
||||||
|
* @param {ICustomerBalanceSummaryCustomer[]} nodes
|
||||||
|
* @returns {ICustomerBalanceSummaryCustomer[]}
|
||||||
|
*/
|
||||||
|
protected contactsFilter = (
|
||||||
|
nodes: ITransactionsByContactsContact[]
|
||||||
|
): ITransactionsByContactsContact[] => {
|
||||||
|
return nodes.filter(this.contactNodeFilter);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { INumberFormatQuery } from "../../types/Report.types";
|
||||||
|
|
||||||
|
|
||||||
|
export interface ITransactionsByContactsAmount {
|
||||||
|
amount: number;
|
||||||
|
formattedAmount: string;
|
||||||
|
currencyCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITransactionsByContactsTransaction {
|
||||||
|
date: string|Date,
|
||||||
|
credit: ITransactionsByContactsAmount;
|
||||||
|
debit: ITransactionsByContactsAmount;
|
||||||
|
accountName: string,
|
||||||
|
runningBalance: ITransactionsByContactsAmount;
|
||||||
|
currencyCode: string;
|
||||||
|
transactionType: string;
|
||||||
|
transactionNumber: string;
|
||||||
|
createdAt: string|Date,
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ITransactionsByContactsContact {
|
||||||
|
openingBalance: ITransactionsByContactsAmount,
|
||||||
|
closingBalance: ITransactionsByContactsAmount,
|
||||||
|
transactions: ITransactionsByContactsTransaction[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITransactionsByContactsFilter {
|
||||||
|
fromDate: Date|string;
|
||||||
|
toDate: Date|string;
|
||||||
|
numberFormat: INumberFormatQuery;
|
||||||
|
noneTransactions: boolean;
|
||||||
|
noneZero: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||||
|
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||||
|
|
||||||
|
export class TransactionsByContactRepository {
|
||||||
|
/**
|
||||||
|
* Base currency.
|
||||||
|
* @param {string} baseCurrency
|
||||||
|
*/
|
||||||
|
public baseCurrency: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report data.
|
||||||
|
*/
|
||||||
|
public accountsGraph: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report data.
|
||||||
|
* @param {Ledger} ledger
|
||||||
|
*/
|
||||||
|
public ledger: Ledger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opening balance entries.
|
||||||
|
* @param {ILedgerEntry[]} openingBalanceEntries
|
||||||
|
*/
|
||||||
|
public openingBalanceEntries: ILedgerEntry[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { ITransactionsByContactsContact } from './TransactionsByContact.types';
|
||||||
|
import { ITableRow } from '../../types/Table.types';
|
||||||
|
import { tableMapper, tableRowMapper } from '../../utils/Table.utils';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
|
||||||
|
enum ROW_TYPE {
|
||||||
|
OPENING_BALANCE = 'OPENING_BALANCE',
|
||||||
|
CLOSING_BALANCE = 'CLOSING_BALANCE',
|
||||||
|
TRANSACTION = 'TRANSACTION',
|
||||||
|
CUSTOMER = 'CUSTOMER',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TransactionsByContactsTableRows {
|
||||||
|
public i18n: I18nService;
|
||||||
|
|
||||||
|
public dateAccessor = (value): string => {
|
||||||
|
return moment(value.date).format('YYYY MMM DD');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the table rows of contact transactions.
|
||||||
|
* @param {ITransactionsByCustomersCustomer} contact
|
||||||
|
* @returns {ITableRow[]}
|
||||||
|
*/
|
||||||
|
public contactTransactions = (
|
||||||
|
contact: ITransactionsByContactsContact,
|
||||||
|
): ITableRow[] => {
|
||||||
|
const columns = [
|
||||||
|
{ key: 'date', accessor: this.dateAccessor },
|
||||||
|
{ key: 'account', accessor: 'accountName' },
|
||||||
|
{ key: 'transactionType', accessor: 'transactionType' },
|
||||||
|
{ key: 'transactionNumber', accessor: 'transactionNumber' },
|
||||||
|
{ key: 'credit', accessor: 'credit.formattedAmount' },
|
||||||
|
{ key: 'debit', accessor: 'debit.formattedAmount' },
|
||||||
|
{ key: 'runningBalance', accessor: 'runningBalance.formattedAmount' },
|
||||||
|
];
|
||||||
|
return tableMapper(contact.transactions, columns, {
|
||||||
|
rowTypes: [ROW_TYPE.TRANSACTION],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the table row of contact opening balance.
|
||||||
|
* @param {ITransactionsByCustomersCustomer} contact
|
||||||
|
* @returns {ITableRow}
|
||||||
|
*/
|
||||||
|
public contactOpeningBalance = (
|
||||||
|
contact: ITransactionsByContactsContact,
|
||||||
|
): ITableRow => {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
key: 'openingBalanceLabel',
|
||||||
|
value: this.i18n.t('Opening balance') as string,
|
||||||
|
},
|
||||||
|
...R.repeat({ key: 'empty', value: '' }, 5),
|
||||||
|
{
|
||||||
|
key: 'openingBalanceValue',
|
||||||
|
accessor: 'openingBalance.formattedAmount',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return tableRowMapper(contact, columns, {
|
||||||
|
rowTypes: [ROW_TYPE.OPENING_BALANCE],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the table row of contact closing balance.
|
||||||
|
* @param {ITransactionsByCustomersCustomer} contact -
|
||||||
|
* @returns {ITableRow}
|
||||||
|
*/
|
||||||
|
public contactClosingBalance = (
|
||||||
|
contact: ITransactionsByContactsContact,
|
||||||
|
): ITableRow => {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
key: 'closingBalanceLabel',
|
||||||
|
value: this.i18n.t('Closing balance') as string,
|
||||||
|
},
|
||||||
|
...R.repeat({ key: 'empty', value: '' }, 5),
|
||||||
|
{
|
||||||
|
key: 'closingBalanceValue',
|
||||||
|
accessor: 'closingBalance.formattedAmount',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return tableRowMapper(contact, columns, {
|
||||||
|
rowTypes: [ROW_TYPE.CLOSING_BALANCE],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TransactionsByCustomersExportInjectable } from './TransactionsByCustomersExportInjectable';
|
||||||
|
import { TransactionsByCustomersPdf } from './TransactionsByCustomersPdf';
|
||||||
|
import { TransactionsByCustomersRepository } from './TransactionsByCustomersRepository';
|
||||||
|
import { TransactionsByCustomersSheet } from './TransactionsByCustomersService';
|
||||||
|
import { TransactionsByCustomersTableInjectable } from './TransactionsByCustomersTableInjectable';
|
||||||
|
import { TransactionsByCustomersMeta } from './TransactionsByCustomersMeta';
|
||||||
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module';
|
||||||
|
import { AccountsModule } from '@/modules/Accounts/Accounts.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [FinancialSheetCommonModule, AccountsModule],
|
||||||
|
providers: [
|
||||||
|
TransactionsByCustomersRepository,
|
||||||
|
TransactionsByCustomersTableInjectable,
|
||||||
|
TransactionsByCustomersExportInjectable,
|
||||||
|
TransactionsByCustomersSheet,
|
||||||
|
TransactionsByCustomersPdf,
|
||||||
|
TransactionsByCustomersMeta,
|
||||||
|
TenancyContext
|
||||||
|
],
|
||||||
|
controllers: [],
|
||||||
|
})
|
||||||
|
export class TransactionsByCustomerModule {}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
import { IFinancialSheetCommonMeta } from '../../types/Report.types';
|
||||||
|
import { IFinancialTable } from '../../types/Table.types';
|
||||||
|
import {
|
||||||
|
ITransactionsByContactsAmount,
|
||||||
|
ITransactionsByContactsTransaction,
|
||||||
|
ITransactionsByContactsFilter,
|
||||||
|
} from '../TransactionsByContact/TransactionsByContact.types';
|
||||||
|
|
||||||
|
export interface ITransactionsByCustomersAmount
|
||||||
|
extends ITransactionsByContactsAmount {}
|
||||||
|
|
||||||
|
export interface ITransactionsByCustomersTransaction
|
||||||
|
extends ITransactionsByContactsTransaction {}
|
||||||
|
|
||||||
|
export interface ITransactionsByCustomersCustomer {
|
||||||
|
customerName: string;
|
||||||
|
openingBalance: ITransactionsByCustomersAmount;
|
||||||
|
closingBalance: ITransactionsByCustomersAmount;
|
||||||
|
transactions: ITransactionsByCustomersTransaction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITransactionsByCustomersFilter
|
||||||
|
extends ITransactionsByContactsFilter {
|
||||||
|
customersIds: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ITransactionsByCustomersData = ITransactionsByCustomersCustomer[];
|
||||||
|
|
||||||
|
export interface ITransactionsByCustomersStatement {
|
||||||
|
data: ITransactionsByCustomersData;
|
||||||
|
query: ITransactionsByCustomersFilter;
|
||||||
|
meta: ITransactionsByCustomersMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITransactionsByCustomersTable extends IFinancialTable {
|
||||||
|
query: ITransactionsByCustomersFilter;
|
||||||
|
meta: ITransactionsByCustomersMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITransactionsByCustomersService {
|
||||||
|
transactionsByCustomers(
|
||||||
|
tenantId: number,
|
||||||
|
filter: ITransactionsByCustomersFilter
|
||||||
|
): Promise<ITransactionsByCustomersStatement>;
|
||||||
|
}
|
||||||
|
export interface ITransactionsByCustomersMeta
|
||||||
|
extends IFinancialSheetCommonMeta {
|
||||||
|
formattedFromDate: string;
|
||||||
|
formattedToDate: string;
|
||||||
|
formattedDateRange: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { ModelObject } from 'objection';
|
||||||
|
import {
|
||||||
|
ITransactionsByCustomersTransaction,
|
||||||
|
ITransactionsByCustomersFilter,
|
||||||
|
ITransactionsByCustomersCustomer,
|
||||||
|
ITransactionsByCustomersData,
|
||||||
|
} from './TransactionsByCustomer.types';
|
||||||
|
import { TransactionsByContact } from '../TransactionsByContact/TransactionsByContact';
|
||||||
|
import { Customer } from '@/modules/Customers/models/Customer';
|
||||||
|
import { INumberFormatQuery } from '../../types/Report.types';
|
||||||
|
import { TransactionsByCustomersRepository } from './TransactionsByCustomersRepository';
|
||||||
|
|
||||||
|
const CUSTOMER_NORMAL = 'debit';
|
||||||
|
|
||||||
|
export class TransactionsByCustomers extends TransactionsByContact {
|
||||||
|
readonly filter: ITransactionsByCustomersFilter;
|
||||||
|
readonly numberFormat: INumberFormatQuery;
|
||||||
|
readonly repository: TransactionsByCustomersRepository;
|
||||||
|
readonly i18n: I18nService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param {ICustomer} customers
|
||||||
|
* @param {Map<number, IAccountTransaction[]>} transactionsLedger
|
||||||
|
* @param {string} baseCurrency
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
filter: ITransactionsByCustomersFilter,
|
||||||
|
transactionsByCustomersRepository: TransactionsByCustomersRepository,
|
||||||
|
i18n: I18nService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.filter = filter;
|
||||||
|
this.repository = transactionsByCustomersRepository;
|
||||||
|
this.numberFormat = this.filter.numberFormat;
|
||||||
|
this.i18n = i18n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the customer transactions from the given customer id and opening balance.
|
||||||
|
* @param {number} customerId - Customer id.
|
||||||
|
* @param {number} openingBalance - Opening balance amount.
|
||||||
|
* @returns {ITransactionsByCustomersTransaction[]}
|
||||||
|
*/
|
||||||
|
private customerTransactions(
|
||||||
|
customerId: number,
|
||||||
|
openingBalance: number
|
||||||
|
): ITransactionsByCustomersTransaction[] {
|
||||||
|
const ledger = this.repository.ledger
|
||||||
|
.whereContactId(customerId)
|
||||||
|
.whereFromDate(this.filter.fromDate)
|
||||||
|
.whereToDate(this.filter.toDate);
|
||||||
|
|
||||||
|
const ledgerEntries = ledger.getEntries();
|
||||||
|
|
||||||
|
return R.compose(
|
||||||
|
R.curry(this.contactTransactionRunningBalance)(openingBalance, 'debit'),
|
||||||
|
R.map(this.contactTransactionMapper.bind(this))
|
||||||
|
).bind(this)(ledgerEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer section mapper.
|
||||||
|
* @param {ModelObject<Customer>} customer
|
||||||
|
* @returns {ITransactionsByCustomersCustomer}
|
||||||
|
*/
|
||||||
|
private customerMapper(
|
||||||
|
customer: ModelObject<Customer>
|
||||||
|
): ITransactionsByCustomersCustomer {
|
||||||
|
const openingBalance = this.getContactOpeningBalance(customer.id);
|
||||||
|
const transactions = this.customerTransactions(customer.id, openingBalance);
|
||||||
|
const closingBalance = this.getCustomerClosingBalance(
|
||||||
|
transactions,
|
||||||
|
openingBalance
|
||||||
|
);
|
||||||
|
const currencyCode = this.baseCurrency;
|
||||||
|
|
||||||
|
return {
|
||||||
|
customerName: customer.displayName,
|
||||||
|
openingBalance: this.getTotalAmountMeta(openingBalance, currencyCode),
|
||||||
|
closingBalance: this.getTotalAmountMeta(closingBalance, currencyCode),
|
||||||
|
transactions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the vendor closing balance from the given customer transactions.
|
||||||
|
* @param {ITransactionsByContactsTransaction[]} customerTransactions
|
||||||
|
* @param {number} openingBalance
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getCustomerClosingBalance(
|
||||||
|
customerTransactions: ITransactionsByCustomersTransaction[],
|
||||||
|
openingBalance: number
|
||||||
|
): number {
|
||||||
|
return this.getContactClosingBalance(
|
||||||
|
customerTransactions,
|
||||||
|
CUSTOMER_NORMAL,
|
||||||
|
openingBalance
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the customers post filter is active.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
private isCustomersPostFilter = () => {
|
||||||
|
return isEmpty(this.filter.customersIds);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the customers sections of the report.
|
||||||
|
* @param {ICustomer[]} customers
|
||||||
|
* @returns {ITransactionsByCustomersCustomer[]}
|
||||||
|
*/
|
||||||
|
private customersMapper(
|
||||||
|
customers: ModelObject<Customer>[]
|
||||||
|
): ITransactionsByCustomersCustomer[] {
|
||||||
|
return R.compose(
|
||||||
|
R.when(this.isCustomersPostFilter, this.contactsFilter),
|
||||||
|
R.map(this.customerMapper.bind(this))
|
||||||
|
).bind(this)(customers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the report data.
|
||||||
|
* @returns {ITransactionsByCustomersData}
|
||||||
|
*/
|
||||||
|
public reportData(): ITransactionsByCustomersData {
|
||||||
|
return this.customersMapper(this.repository.customers);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import {
|
||||||
|
ITransactionsByCustomersFilter,
|
||||||
|
ITransactionsByCustomersStatement,
|
||||||
|
} from './TransactionsByCustomer.types';
|
||||||
|
import { TransactionsByCustomersTableInjectable } from './TransactionsByCustomersTableInjectable';
|
||||||
|
import { TransactionsByCustomersExportInjectable } from './TransactionsByCustomersExportInjectable';
|
||||||
|
import { TransactionsByCustomersSheet } from './TransactionsByCustomersService';
|
||||||
|
import { TransactionsByCustomersPdf } from './TransactionsByCustomersPdf';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByCustomerApplication {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionsByCustomersTable: TransactionsByCustomersTableInjectable,
|
||||||
|
private readonly transactionsByCustomersExport: TransactionsByCustomersExportInjectable,
|
||||||
|
private readonly transactionsByCustomersSheet: TransactionsByCustomersSheet,
|
||||||
|
private readonly transactionsByCustomersPdf: TransactionsByCustomersPdf,
|
||||||
|
) {}
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by customers sheet in json format.
|
||||||
|
* @param {ITransactionsByCustomersFilter} query - Transactions by customers filter.
|
||||||
|
* @returns {Promise<ITransactionsByCustomersStatement>}
|
||||||
|
*/
|
||||||
|
public sheet(
|
||||||
|
query: ITransactionsByCustomersFilter,
|
||||||
|
): Promise<ITransactionsByCustomersStatement> {
|
||||||
|
return this.transactionsByCustomersSheet.transactionsByCustomers(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by vendors sheet in table format.
|
||||||
|
* @param {ITransactionsByCustomersFilter} query - Transactions by customers filter.
|
||||||
|
* @returns {Promise<ITransactionsByCustomersTable>}
|
||||||
|
*/
|
||||||
|
public table(query: ITransactionsByCustomersFilter) {
|
||||||
|
return this.transactionsByCustomersTable.table(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by vendors sheet in CSV format.
|
||||||
|
* @param {ITransactionsByCustomersFilter} query - Transactions by customers filter.
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
public csv(query: ITransactionsByCustomersFilter): Promise<string> {
|
||||||
|
return this.transactionsByCustomersExport.csv(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by vendors sheet in XLSX format.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {ITransactionsByCustomersFilter} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public xlsx(query: ITransactionsByCustomersFilter): Promise<Buffer> {
|
||||||
|
return this.transactionsByCustomersExport.xlsx(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by vendors sheet in PDF format.
|
||||||
|
* @param {ITransactionsByCustomersFilter} query - Transactions by customers filter.
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public pdf(query: ITransactionsByCustomersFilter): Promise<Buffer> {
|
||||||
|
return this.transactionsByCustomersPdf.pdf(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { TableSheet } from '../../common/TableSheet';
|
||||||
|
import { ITransactionsByCustomersFilter } from './TransactionsByCustomer.types';
|
||||||
|
import { TransactionsByCustomersTableInjectable } from './TransactionsByCustomersTableInjectable';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByCustomersExportInjectable {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionsByCustomerTable: TransactionsByCustomersTableInjectable,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the cashflow sheet in XLSX format.
|
||||||
|
* @param {ITransactionsByCustomersFilter} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public async xlsx(query: ITransactionsByCustomersFilter): Promise<Buffer> {
|
||||||
|
const table = await this.transactionsByCustomerTable.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 {ITransactionsByCustomersFilter} query
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
public async csv(query: ITransactionsByCustomersFilter): Promise<string> {
|
||||||
|
const table = await this.transactionsByCustomerTable.table(query);
|
||||||
|
|
||||||
|
const tableSheet = new TableSheet(table.table);
|
||||||
|
const tableCsv = tableSheet.convertToCSV();
|
||||||
|
|
||||||
|
return tableCsv;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { FinancialSheetMeta } from '../../common/FinancialSheetMeta';
|
||||||
|
import {
|
||||||
|
ITransactionsByCustomersFilter,
|
||||||
|
ITransactionsByCustomersMeta,
|
||||||
|
} from './TransactionsByCustomer.types';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByCustomersMeta {
|
||||||
|
constructor(private readonly financialSheetMeta: FinancialSheetMeta) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by customers meta.
|
||||||
|
* @param {ITransactionsByCustomersFilter} query - Transactions by customers filter.
|
||||||
|
* @returns {ITransactionsByCustomersMeta}
|
||||||
|
*/
|
||||||
|
public async meta(
|
||||||
|
query: ITransactionsByCustomersFilter,
|
||||||
|
): Promise<ITransactionsByCustomersMeta> {
|
||||||
|
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}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...commonMeta,
|
||||||
|
sheetName: 'Transactions By Customers',
|
||||||
|
formattedFromDate,
|
||||||
|
formattedToDate,
|
||||||
|
formattedDateRange,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { TableSheetPdf } from '../../common/TableSheetPdf';
|
||||||
|
import { ITransactionsByCustomersFilter } from './TransactionsByCustomer.types';
|
||||||
|
import { TransactionsByCustomersTableInjectable } from './TransactionsByCustomersTableInjectable';
|
||||||
|
|
||||||
|
export class TransactionsByCustomersPdf {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionsByCustomersTable: TransactionsByCustomersTableInjectable,
|
||||||
|
private readonly tableSheetPdf: TableSheetPdf,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by customers in PDF format.
|
||||||
|
* @param {ITransactionsByCustomersFilter} query - Transactions by customers filter.
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public async pdf(query: ITransactionsByCustomersFilter): Promise<Buffer> {
|
||||||
|
const table = await this.transactionsByCustomersTable.table(query);
|
||||||
|
|
||||||
|
return this.tableSheetPdf.convertToPdf(
|
||||||
|
table.table,
|
||||||
|
table.meta.sheetName,
|
||||||
|
table.meta.formattedDateRange,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { ACCOUNT_TYPE } from '@/constants/accounts';
|
||||||
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
|
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
||||||
|
import { Customer } from '@/modules/Customers/models/Customer';
|
||||||
|
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||||
|
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||||
|
import { isEmpty, map } from 'lodash';
|
||||||
|
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
|
||||||
|
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||||
|
import { ITransactionsByCustomersFilter } from './TransactionsByCustomer.types';
|
||||||
|
import { ModelObject } from 'objection';
|
||||||
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
import { TransactionsByContactRepository } from '../TransactionsByContact/TransactionsByContactRepository';
|
||||||
|
|
||||||
|
@Injectable({ scope: Scope.TRANSIENT })
|
||||||
|
export class TransactionsByCustomersRepository extends TransactionsByContactRepository {
|
||||||
|
@Inject(Customer.name)
|
||||||
|
private readonly customerModel: typeof Customer;
|
||||||
|
|
||||||
|
@Inject(Account.name)
|
||||||
|
private readonly accountModel: typeof Account;
|
||||||
|
|
||||||
|
@Inject(AccountTransaction.name)
|
||||||
|
private readonly accountTransactionModel: typeof AccountTransaction;
|
||||||
|
|
||||||
|
@Inject(AccountRepository)
|
||||||
|
private readonly accountRepository: AccountRepository;
|
||||||
|
|
||||||
|
@Inject(TenancyContext)
|
||||||
|
private readonly tenancyContext: TenancyContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customers models.
|
||||||
|
* @param {ModelObject<Customer>[]} customers
|
||||||
|
*/
|
||||||
|
public customers: ModelObject<Customer>[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report filter.
|
||||||
|
* @param {ITransactionsByCustomersFilter} filter
|
||||||
|
*/
|
||||||
|
public filter: ITransactionsByCustomersFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customers periods entries.
|
||||||
|
* @param {ILedgerEntry[]} customersPeriodsEntries
|
||||||
|
*/
|
||||||
|
public customersPeriodsEntries: ILedgerEntry[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the report data.
|
||||||
|
* @param {ITransactionsByCustomersFilter} filter
|
||||||
|
*/
|
||||||
|
public async asyncInit(filter: ITransactionsByCustomersFilter) {
|
||||||
|
this.filter = filter;
|
||||||
|
|
||||||
|
await this.initAccountsGraph();
|
||||||
|
await this.initCustomers();
|
||||||
|
await this.initOpeningBalanceEntries();
|
||||||
|
await this.initCustomersPeriodsEntries();
|
||||||
|
await this.initLedger();
|
||||||
|
await this.initBaseCurrency();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the accounts graph.
|
||||||
|
*/
|
||||||
|
async initAccountsGraph() {
|
||||||
|
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||||
|
this.accountsGraph = accountsGraph;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the customers.
|
||||||
|
*/
|
||||||
|
async initCustomers() {
|
||||||
|
// Retrieve the report customers.
|
||||||
|
const customers = await this.getCustomers(this.filter.customersIds);
|
||||||
|
this.customers = customers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the opening balance entries.
|
||||||
|
*/
|
||||||
|
async initOpeningBalanceEntries() {
|
||||||
|
const openingBalanceDate = moment(this.filter.fromDate)
|
||||||
|
.subtract(1, 'days')
|
||||||
|
.toDate();
|
||||||
|
|
||||||
|
// Retrieve all ledger transactions of the opening balance of.
|
||||||
|
const openingBalanceEntries =
|
||||||
|
await this.getCustomersOpeningBalanceEntries(openingBalanceDate);
|
||||||
|
|
||||||
|
this.openingBalanceEntries = openingBalanceEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the customers periods entries.
|
||||||
|
*/
|
||||||
|
async initCustomersPeriodsEntries() {
|
||||||
|
// Retrieve all ledger transactions between opeing and closing period.
|
||||||
|
const customersTransactions = await this.getCustomersPeriodsEntries(
|
||||||
|
this.filter.fromDate,
|
||||||
|
this.filter.toDate,
|
||||||
|
);
|
||||||
|
this.customersPeriodsEntries = customersTransactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the ledger.
|
||||||
|
*/
|
||||||
|
async initLedger() {
|
||||||
|
// Concats the opening balance and period customer ledger transactions.
|
||||||
|
const journalTransactions = [
|
||||||
|
...this.openingBalanceEntries,
|
||||||
|
...this.customersPeriodsEntries,
|
||||||
|
];
|
||||||
|
this.ledger = new Ledger(journalTransactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
async initBaseCurrency() {
|
||||||
|
const tenantMetadata = await this.tenancyContext.getTenantMetadata();
|
||||||
|
this.baseCurrency = tenantMetadata.baseCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the customers opening balance ledger entries.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {Date} openingDate
|
||||||
|
* @param {number[]} customersIds
|
||||||
|
* @returns {Promise<ILedgerEntry[]>}
|
||||||
|
*/
|
||||||
|
private async getCustomersOpeningBalanceEntries(
|
||||||
|
openingDate: Date,
|
||||||
|
customersIds?: number[],
|
||||||
|
): Promise<ILedgerEntry[]> {
|
||||||
|
const openingTransactions =
|
||||||
|
await this.getCustomersOpeningBalanceTransactions(
|
||||||
|
openingDate,
|
||||||
|
customersIds,
|
||||||
|
);
|
||||||
|
return R.compose(
|
||||||
|
R.map(R.assoc('date', openingDate)),
|
||||||
|
R.map(R.assoc('accountNormal', 'debit')),
|
||||||
|
)(openingTransactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the customers periods ledger entries.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {Date} fromDate
|
||||||
|
* @param {Date} toDate
|
||||||
|
* @returns {Promise<ILedgerEntry[]>}
|
||||||
|
*/
|
||||||
|
private async getCustomersPeriodsEntries(
|
||||||
|
fromDate: Date | string,
|
||||||
|
toDate: Date | string,
|
||||||
|
): Promise<ILedgerEntry[]> {
|
||||||
|
const transactions =
|
||||||
|
await this.getCustomersPeriodTransactions(
|
||||||
|
fromDate,
|
||||||
|
toDate,
|
||||||
|
);
|
||||||
|
return R.compose(
|
||||||
|
R.map(R.assoc('accountNormal', 'debit')),
|
||||||
|
R.map((trans) => ({
|
||||||
|
...trans,
|
||||||
|
referenceTypeFormatted: trans.referenceTypeFormatted,
|
||||||
|
})),
|
||||||
|
)(transactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the report customers.
|
||||||
|
* @param {number[]} customersIds - Customers ids.
|
||||||
|
* @returns {Promise<ICustomer[]>}
|
||||||
|
*/
|
||||||
|
public async getCustomers(customersIds?: number[]) {
|
||||||
|
return this.customerModel.query().onBuild((q) => {
|
||||||
|
q.orderBy('displayName');
|
||||||
|
|
||||||
|
if (!isEmpty(customersIds)) {
|
||||||
|
q.whereIn('id', customersIds);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the accounts receivable.
|
||||||
|
* @returns {Promise<IAccount[]>}
|
||||||
|
*/
|
||||||
|
public async getReceivableAccounts(): Promise<Account[]> {
|
||||||
|
const accounts = await this.accountModel
|
||||||
|
.query()
|
||||||
|
.where('accountType', ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE);
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the customers opening balance transactions.
|
||||||
|
* @param {number} openingDate - Opening date.
|
||||||
|
* @param {number} customersIds - Customers ids.
|
||||||
|
* @returns {Promise<IAccountTransaction[]>}
|
||||||
|
*/
|
||||||
|
public async getCustomersOpeningBalanceTransactions(
|
||||||
|
openingDate: Date,
|
||||||
|
customersIds?: number[],
|
||||||
|
): Promise<AccountTransaction[]> {
|
||||||
|
const receivableAccounts = await this.getReceivableAccounts();
|
||||||
|
const receivableAccountsIds = map(receivableAccounts, 'id');
|
||||||
|
|
||||||
|
const openingTransactions = await this.accountTransactionModel
|
||||||
|
.query()
|
||||||
|
.modify(
|
||||||
|
'contactsOpeningBalance',
|
||||||
|
openingDate,
|
||||||
|
receivableAccountsIds,
|
||||||
|
customersIds,
|
||||||
|
);
|
||||||
|
return openingTransactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the customers periods transactions.
|
||||||
|
* @param {Date} fromDate - From date.
|
||||||
|
* @param {Date} toDate - To date.
|
||||||
|
* @return {Promise<IAccountTransaction[]>}
|
||||||
|
*/
|
||||||
|
public async getCustomersPeriodTransactions(
|
||||||
|
fromDate: Date,
|
||||||
|
toDate: Date,
|
||||||
|
): Promise<AccountTransaction[]> {
|
||||||
|
const receivableAccounts = await this.getReceivableAccounts();
|
||||||
|
const receivableAccountsIds = map(receivableAccounts, 'id');
|
||||||
|
|
||||||
|
const transactions = await this.accountTransactionModel
|
||||||
|
.query()
|
||||||
|
.onBuild((query) => {
|
||||||
|
// Filter by date.
|
||||||
|
query.modify('filterDateRange', fromDate, toDate);
|
||||||
|
|
||||||
|
// Filter by customers.
|
||||||
|
query.whereNot('contactId', null);
|
||||||
|
|
||||||
|
// Filter by accounts.
|
||||||
|
query.whereIn('accountId', receivableAccountsIds);
|
||||||
|
});
|
||||||
|
return transactions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import {
|
||||||
|
ITransactionsByCustomersFilter,
|
||||||
|
ITransactionsByCustomersStatement,
|
||||||
|
} from './TransactionsByCustomer.types';
|
||||||
|
import { TransactionsByCustomersRepository } from './TransactionsByCustomersRepository';
|
||||||
|
import { TransactionsByCustomersMeta } from './TransactionsByCustomersMeta';
|
||||||
|
import { getTransactionsByCustomerDefaultQuery } from './utils';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { TransactionsByCustomers } from './TransactionsByCustomers';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByCustomersSheet {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionsByCustomersMeta: TransactionsByCustomersMeta,
|
||||||
|
private readonly transactionsByCustomersRepository: TransactionsByCustomersRepository,
|
||||||
|
private readonly eventPublisher: EventEmitter2,
|
||||||
|
private readonly tenancyContext: TenancyContext,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve transactions by by the customers.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {ITransactionsByCustomersFilter} query
|
||||||
|
* @return {Promise<ITransactionsByCustomersStatement>}
|
||||||
|
*/
|
||||||
|
public async transactionsByCustomers(
|
||||||
|
query: ITransactionsByCustomersFilter,
|
||||||
|
): Promise<ITransactionsByCustomersStatement> {
|
||||||
|
const tenantMetadata = await this.tenancyContext.getTenantMetadata();
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
...getTransactionsByCustomerDefaultQuery(),
|
||||||
|
...query,
|
||||||
|
};
|
||||||
|
await this.transactionsByCustomersRepository.asyncInit(filter);
|
||||||
|
|
||||||
|
// Transactions by customers data mapper.
|
||||||
|
const reportInstance = new TransactionsByCustomers(
|
||||||
|
filter,
|
||||||
|
this.transactionsByCustomersRepository,
|
||||||
|
this.i18n,
|
||||||
|
);
|
||||||
|
|
||||||
|
const meta = await this.transactionsByCustomersMeta.meta(filter);
|
||||||
|
|
||||||
|
// Triggers `onCustomerTransactionsViewed` event.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.reports.onCustomerTransactionsViewed,
|
||||||
|
{
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: reportInstance.reportData(),
|
||||||
|
query: filter,
|
||||||
|
meta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { ITransactionsByCustomersCustomer } from './TransactionsByCustomer.types';
|
||||||
|
import { ITableRow, ITableColumn } from '../../types/Table.types';
|
||||||
|
import { TransactionsByContactsTableRows } from '../TransactionsByContact/TransactionsByContactTableRows';
|
||||||
|
import { tableRowMapper } from '../../utils/Table.utils';
|
||||||
|
|
||||||
|
enum ROW_TYPE {
|
||||||
|
OPENING_BALANCE = 'OPENING_BALANCE',
|
||||||
|
CLOSING_BALANCE = 'CLOSING_BALANCE',
|
||||||
|
TRANSACTION = 'TRANSACTION',
|
||||||
|
CUSTOMER = 'CUSTOMER',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TransactionsByCustomersTable extends TransactionsByContactsTableRows {
|
||||||
|
private customersTransactions: ITransactionsByCustomersCustomer[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param {ITransactionsByCustomersCustomer[]} customersTransactions - Customers transactions.
|
||||||
|
*/
|
||||||
|
constructor(customersTransactions: ITransactionsByCustomersCustomer[], i18n) {
|
||||||
|
super();
|
||||||
|
this.customersTransactions = customersTransactions;
|
||||||
|
this.i18n = i18n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the table row of customer details.
|
||||||
|
* @param {ITransactionsByCustomersCustomer} customer -
|
||||||
|
* @returns {ITableRow[]}
|
||||||
|
*/
|
||||||
|
private customerDetails = (customer: ITransactionsByCustomersCustomer) => {
|
||||||
|
const columns = [
|
||||||
|
{ key: 'customerName', accessor: 'customerName' },
|
||||||
|
...R.repeat({ key: 'empty', value: '' }, 5),
|
||||||
|
{
|
||||||
|
key: 'closingBalanceValue',
|
||||||
|
accessor: 'closingBalance.formattedAmount',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...tableRowMapper(customer, columns, { rowTypes: [ROW_TYPE.CUSTOMER] }),
|
||||||
|
children: R.pipe(
|
||||||
|
R.when(
|
||||||
|
R.always(customer.transactions.length > 0),
|
||||||
|
R.pipe(
|
||||||
|
R.concat(this.contactTransactions(customer)),
|
||||||
|
R.prepend(this.contactOpeningBalance(customer)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
R.append(this.contactClosingBalance(customer)),
|
||||||
|
)([]),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the table rows of the customer section.
|
||||||
|
* @param {ITransactionsByCustomersCustomer} customer
|
||||||
|
* @returns {ITableRow[]}
|
||||||
|
*/
|
||||||
|
private customerRowsMapper = (customer: ITransactionsByCustomersCustomer) => {
|
||||||
|
return R.pipe(this.customerDetails)(customer);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the table rows of transactions by customers report.
|
||||||
|
* @param {ITransactionsByCustomersCustomer[]} customers
|
||||||
|
* @returns {ITableRow[]}
|
||||||
|
*/
|
||||||
|
public tableRows = (): ITableRow[] => {
|
||||||
|
return R.map(this.customerRowsMapper.bind(this))(
|
||||||
|
this.customersTransactions,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the table columns of transactions by customers report.
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
public tableColumns = (): ITableColumn[] => {
|
||||||
|
return [
|
||||||
|
{ key: 'customer_name', label: 'Customer name' },
|
||||||
|
{ key: 'account_name', label: 'Account Name' },
|
||||||
|
{ key: 'ref_type', label: 'Reference Type' },
|
||||||
|
{ key: 'transaction_type', label: 'Transaction Type' },
|
||||||
|
{ key: 'credit', label: 'Credit' },
|
||||||
|
{ key: 'debit', label: 'Debit' },
|
||||||
|
{ key: 'running_balance', label: 'Running Balance' },
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import {
|
||||||
|
ITransactionsByCustomersFilter,
|
||||||
|
ITransactionsByCustomersTable,
|
||||||
|
} from './TransactionsByCustomer.types';
|
||||||
|
import { TransactionsByCustomersSheet } from './TransactionsByCustomersService';
|
||||||
|
import { TransactionsByCustomersTable } from './TransactionsByCustomersTable';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByCustomersTableInjectable {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionsByCustomerService: TransactionsByCustomersSheet,
|
||||||
|
private readonly i18n: I18nService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by customers sheet in table format.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {ITransactionsByCustomersFilter} filter
|
||||||
|
* @returns {Promise<ITransactionsByCustomersFilter>}
|
||||||
|
*/
|
||||||
|
public async table(
|
||||||
|
filter: ITransactionsByCustomersFilter,
|
||||||
|
): Promise<ITransactionsByCustomersTable> {
|
||||||
|
const customersTransactions =
|
||||||
|
await this.transactionsByCustomerService.transactionsByCustomers(filter);
|
||||||
|
|
||||||
|
const table = new TransactionsByCustomersTable(
|
||||||
|
customersTransactions.data,
|
||||||
|
this.i18n,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
table: {
|
||||||
|
rows: table.tableRows(),
|
||||||
|
columns: table.tableColumns(),
|
||||||
|
},
|
||||||
|
query: customersTransactions.query,
|
||||||
|
meta: customersTransactions.meta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getTransactionsByCustomerDefaultQuery = () => {
|
||||||
|
return {
|
||||||
|
fromDate: moment().startOf('month').format('YYYY-MM-DD'),
|
||||||
|
toDate: moment().format('YYYY-MM-DD'),
|
||||||
|
numberFormat: {
|
||||||
|
precision: 2,
|
||||||
|
divideOn1000: false,
|
||||||
|
showZero: false,
|
||||||
|
formatMoney: 'total',
|
||||||
|
negativeFormat: 'mines',
|
||||||
|
},
|
||||||
|
comparison: {
|
||||||
|
percentageOfColumn: true,
|
||||||
|
},
|
||||||
|
noneZero: false,
|
||||||
|
noneTransactions: true,
|
||||||
|
customersIds: [],
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TransactionsByReferenceApplication } from './TransactionsByReferenceApplication';
|
||||||
|
import { TransactionsByReferenceRepository } from './TransactionsByReferenceRepository';
|
||||||
|
import { TransactionsByReferenceService } from './TransactionsByReference.service';
|
||||||
|
import { TransactionsByReferenceController } from './TransactionsByReference.controller';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [
|
||||||
|
TransactionsByReferenceRepository,
|
||||||
|
TransactionsByReferenceApplication,
|
||||||
|
TransactionsByReferenceService,
|
||||||
|
],
|
||||||
|
controllers: [TransactionsByReferenceController],
|
||||||
|
})
|
||||||
|
export class TransactionsByReferenceModule {}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Controller, Get, Query } from '@nestjs/common';
|
||||||
|
import { TransactionsByReferenceApplication } from './TransactionsByReferenceApplication';
|
||||||
|
import { ITransactionsByReferenceQuery } from './TransactionsByReference.types';
|
||||||
|
|
||||||
|
@Controller('reports/transactions-by-reference')
|
||||||
|
export class TransactionsByReferenceController {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionsByReferenceApp: TransactionsByReferenceApplication,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async getTransactionsByReference(
|
||||||
|
@Query() query: ITransactionsByReferenceQuery,
|
||||||
|
) {
|
||||||
|
const data = await this.transactionsByReferenceApp.getTransactions(query);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
ITransactionsByReferencePojo,
|
||||||
|
ITransactionsByReferenceQuery,
|
||||||
|
} from './TransactionsByReference.types';
|
||||||
|
import { TransactionsByReferenceRepository } from './TransactionsByReferenceRepository';
|
||||||
|
import { TransactionsByReference } from './TransactionsByReferenceReport';
|
||||||
|
import { getTransactionsByReferenceQuery } from './_utils';
|
||||||
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByReferenceService {
|
||||||
|
constructor(
|
||||||
|
private readonly repository: TransactionsByReferenceRepository,
|
||||||
|
private readonly tenancyContext: TenancyContext
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts transactions by given reference id and type.
|
||||||
|
* @param {ITransactionsByReferenceQuery} filter - Transactions by reference query.
|
||||||
|
* @returns {Promise<ITransactionsByReferencePojo>}
|
||||||
|
*/
|
||||||
|
public async getTransactionsByReference(
|
||||||
|
query: ITransactionsByReferenceQuery
|
||||||
|
): Promise<ITransactionsByReferencePojo> {
|
||||||
|
const filter = {
|
||||||
|
...getTransactionsByReferenceQuery(),
|
||||||
|
...query,
|
||||||
|
};
|
||||||
|
const tenantMetadata = await this.tenancyContext.getTenantMetadata();
|
||||||
|
|
||||||
|
// Retrieve the accounts transactions of the given reference.
|
||||||
|
const transactions = await this.repository.getTransactions(
|
||||||
|
Number(filter.referenceId),
|
||||||
|
filter.referenceType
|
||||||
|
);
|
||||||
|
// Transactions by reference report.
|
||||||
|
const report = new TransactionsByReference(
|
||||||
|
transactions,
|
||||||
|
filter,
|
||||||
|
tenantMetadata.baseCurrency
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
transactions: report.reportData(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
export interface ITransactionsByReferenceQuery {
|
||||||
|
referenceType: string;
|
||||||
|
referenceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITransactionsByReferenceAmount {
|
||||||
|
amount: number;
|
||||||
|
formattedAmount: string;
|
||||||
|
currencyCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITransactionsByReferenceTransaction {
|
||||||
|
credit: ITransactionsByReferenceAmount;
|
||||||
|
debit: ITransactionsByReferenceAmount;
|
||||||
|
|
||||||
|
contactType: string;
|
||||||
|
formattedContactType: string;
|
||||||
|
|
||||||
|
contactId: number;
|
||||||
|
|
||||||
|
referenceType: string;
|
||||||
|
formattedReferenceType: string;
|
||||||
|
|
||||||
|
referenceId: number;
|
||||||
|
|
||||||
|
accountName: string;
|
||||||
|
accountCode: string;
|
||||||
|
accountId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ITransactionsByReferencePojo {
|
||||||
|
transactions: ITransactionsByReferenceTransaction[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { TransactionsByReferenceService } from './TransactionsByReference.service';
|
||||||
|
import { ITransactionsByReferenceQuery } from './TransactionsByReference.types';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByReferenceApplication {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionsByReferenceService: TransactionsByReferenceService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts transactions by given reference id and type.
|
||||||
|
* @param {ITransactionsByReferenceQuery} query - Transactions by reference query.
|
||||||
|
* @returns {Promise<ITransactionsByReferencePojo>}
|
||||||
|
*/
|
||||||
|
public async getTransactions(query: ITransactionsByReferenceQuery) {
|
||||||
|
return this.transactionsByReferenceService.getTransactionsByReference(
|
||||||
|
query,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import {
|
||||||
|
ITransactionsByReferenceQuery,
|
||||||
|
ITransactionsByReferenceTransaction,
|
||||||
|
} from './TransactionsByReference.types';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { ModelObject } from 'objection';
|
||||||
|
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
||||||
|
import { INumberFormatQuery } from '../../types/Report.types';
|
||||||
|
|
||||||
|
export class TransactionsByReference extends FinancialSheet {
|
||||||
|
readonly transactions: ModelObject<AccountTransaction>[];
|
||||||
|
readonly query: ITransactionsByReferenceQuery;
|
||||||
|
readonly baseCurrency: string;
|
||||||
|
readonly numberFormat: INumberFormatQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param {ModelObject<AccountTransaction>[]} transactions
|
||||||
|
* @param {ITransactionsByReferenceQuery} query
|
||||||
|
* @param {string} baseCurrency
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
transactions: ModelObject<AccountTransaction>[],
|
||||||
|
query: ITransactionsByReferenceQuery,
|
||||||
|
baseCurrency: string
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.transactions = transactions;
|
||||||
|
this.query = query;
|
||||||
|
this.baseCurrency = baseCurrency;
|
||||||
|
this.numberFormat = this.query.numberFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mappes the given account transaction to report transaction.
|
||||||
|
* @param {IAccountTransaction} transaction
|
||||||
|
* @returns {ITransactionsByReferenceTransaction}
|
||||||
|
*/
|
||||||
|
private transactionMapper = (
|
||||||
|
transaction: ModelObject<AccountTransaction>
|
||||||
|
): ITransactionsByReferenceTransaction => {
|
||||||
|
return {
|
||||||
|
date: this.getDateMeta(transaction.date),
|
||||||
|
|
||||||
|
credit: this.getAmountMeta(transaction.credit, { money: false }),
|
||||||
|
debit: this.getAmountMeta(transaction.debit, { money: false }),
|
||||||
|
|
||||||
|
referenceTypeFormatted: transaction.referenceTypeFormatted,
|
||||||
|
referenceType: transaction.referenceType,
|
||||||
|
referenceId: transaction.referenceId,
|
||||||
|
|
||||||
|
contactId: transaction.contactId,
|
||||||
|
contactType: transaction.contactType,
|
||||||
|
contactTypeFormatted: transaction.contactType,
|
||||||
|
|
||||||
|
accountName: transaction.account.name,
|
||||||
|
accountCode: transaction.account.code,
|
||||||
|
accountId: transaction.accountId,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mappes the given accounts transactions to report transactions.
|
||||||
|
* @param {IAccountTransaction} transaction
|
||||||
|
* @returns {ITransactionsByReferenceTransaction}
|
||||||
|
*/
|
||||||
|
private transactionsMapper = (
|
||||||
|
transactions: ModelObject<AccountTransaction>[]
|
||||||
|
): ITransactionsByReferenceTransaction[] => {
|
||||||
|
return transactions.map(this.transactionMapper);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the report data.
|
||||||
|
* @returns {ITransactionsByReferenceTransaction}
|
||||||
|
*/
|
||||||
|
public reportData(): ITransactionsByReferenceTransaction[] {
|
||||||
|
return this.transactionsMapper(this.transactions);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { ModelObject } from 'objection';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByReferenceRepository {
|
||||||
|
constructor(
|
||||||
|
@Inject(AccountTransaction.name)
|
||||||
|
private readonly accountTransactionModel: typeof AccountTransaction,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the accounts transactions of the givne reference id and type.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {number} referenceId - Reference id.
|
||||||
|
* @param {string} referenceType - Reference type.
|
||||||
|
* @return {Promise<IAccountTransaction[]>}
|
||||||
|
*/
|
||||||
|
public async getTransactions(
|
||||||
|
referenceId: number,
|
||||||
|
referenceType: string,
|
||||||
|
): Promise<Array<ModelObject<AccountTransaction>>> {
|
||||||
|
return this.accountTransactionModel.query()
|
||||||
|
.where('reference_id', referenceId)
|
||||||
|
.where('reference_type', referenceType)
|
||||||
|
.withGraphFetched('account');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getTransactionsByReferenceQuery = () => ({
|
||||||
|
numberFormat: {
|
||||||
|
precision: 2,
|
||||||
|
divideOn1000: false,
|
||||||
|
showZero: false,
|
||||||
|
formatMoney: 'total',
|
||||||
|
negativeFormat: 'mines',
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { Controller, Get, Headers, Query, Res } from '@nestjs/common';
|
||||||
|
import { ITransactionsByVendorsFilter } from './TransactionsByVendor.types';
|
||||||
|
import { AcceptType } from '@/constants/accept-type';
|
||||||
|
import { Response } from 'express';
|
||||||
|
import { TransactionsByVendorApplication } from './TransactionsByVendorApplication';
|
||||||
|
import { ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||||
|
import { PublicRoute } from '@/modules/Auth/Jwt.guard';
|
||||||
|
|
||||||
|
@Controller('/reports/transactions-by-vendors')
|
||||||
|
@PublicRoute()
|
||||||
|
export class TransactionsByVendorController {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionsByVendorsApp: TransactionsByVendorApplication,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Get transactions by vendor' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Transactions by vendor' })
|
||||||
|
async transactionsByVendor(
|
||||||
|
@Query() filter: ITransactionsByVendorsFilter,
|
||||||
|
@Res() res: Response,
|
||||||
|
@Headers('accept') acceptHeader: string,
|
||||||
|
) {
|
||||||
|
// Retrieves the xlsx format.
|
||||||
|
if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
||||||
|
const buffer = await this.transactionsByVendorsApp.xlsx(filter);
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', 'application/vnd.openxmlformats');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename=report.xlsx');
|
||||||
|
|
||||||
|
return res.send(buffer);
|
||||||
|
// Retrieves the csv format.
|
||||||
|
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
||||||
|
const buffer = await this.transactionsByVendorsApp.csv(filter);
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', 'text/csv');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename=report.csv');
|
||||||
|
|
||||||
|
return res.send(buffer);
|
||||||
|
// Retrieves the json table format.
|
||||||
|
} else if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
||||||
|
const table = await this.transactionsByVendorsApp.table(filter);
|
||||||
|
|
||||||
|
return res.status(200).send(table);
|
||||||
|
// Retrieves the pdf format.
|
||||||
|
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
||||||
|
const pdfContent = await this.transactionsByVendorsApp.pdf(filter);
|
||||||
|
res.set({
|
||||||
|
'Content-Type': 'application/pdf',
|
||||||
|
'Content-Length': pdfContent.length,
|
||||||
|
});
|
||||||
|
return res.send(pdfContent);
|
||||||
|
// Retrieves the json format.
|
||||||
|
} else {
|
||||||
|
const sheet = await this.transactionsByVendorsApp.sheet(filter);
|
||||||
|
return res.status(200).send(sheet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TransactionsByVendorController } from './TransactionsByVendor.controller';
|
||||||
|
import { TransactionsByVendorsInjectable } from './TransactionsByVendorInjectable';
|
||||||
|
import { TransactionsByVendorMeta } from './TransactionsByVendorMeta';
|
||||||
|
import { TransactionsByVendorRepository } from './TransactionsByVendorRepository';
|
||||||
|
import { TransactionsByVendorTableInjectable } from './TransactionsByVendorTableInjectable';
|
||||||
|
import { TransactionsByVendorExportInjectable } from './TransactionsByVendorExportInjectable';
|
||||||
|
import { TransactionsByVendorsPdf } from './TransactionsByVendorPdf';
|
||||||
|
import { TransactionsByVendorApplication } from './TransactionsByVendorApplication';
|
||||||
|
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: [TransactionsByVendorController,],
|
||||||
|
providers: [
|
||||||
|
TransactionsByVendorsInjectable,
|
||||||
|
TransactionsByVendorRepository,
|
||||||
|
TransactionsByVendorMeta,
|
||||||
|
TransactionsByVendorTableInjectable,
|
||||||
|
TransactionsByVendorExportInjectable,
|
||||||
|
TransactionsByVendorsPdf,
|
||||||
|
TransactionsByVendorApplication,
|
||||||
|
TenancyContext
|
||||||
|
],
|
||||||
|
exports: [TransactionsByVendorApplication],
|
||||||
|
})
|
||||||
|
export class TransactionsByVendorModule {}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
import { ModelObject } from 'objection';
|
||||||
|
import {
|
||||||
|
ITransactionsByVendorsFilter,
|
||||||
|
ITransactionsByVendorsTransaction,
|
||||||
|
ITransactionsByVendorsVendor,
|
||||||
|
ITransactionsByVendorsData,
|
||||||
|
} from './TransactionsByVendor.types';
|
||||||
|
import { TransactionsByContact } from '../TransactionsByContact/TransactionsByContact';
|
||||||
|
import { Vendor } from '@/modules/Vendors/models/Vendor';
|
||||||
|
import { INumberFormatQuery } from '../../types/Report.types';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { TransactionsByVendorRepository } from './TransactionsByVendorRepository';
|
||||||
|
|
||||||
|
const VENDOR_NORMAL = 'credit';
|
||||||
|
|
||||||
|
export class TransactionsByVendor extends TransactionsByContact {
|
||||||
|
public readonly repository: TransactionsByVendorRepository;
|
||||||
|
public readonly filter: ITransactionsByVendorsFilter;
|
||||||
|
public readonly numberFormat: INumberFormatQuery;
|
||||||
|
public readonly i18n: I18nService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param {TransactionsByVendorRepository} transactionsByVendorRepository - Transactions by vendor repository.
|
||||||
|
* @param {ITransactionsByVendorsFilter} filter - Transactions by vendors filter.
|
||||||
|
* @param {I18nService} i18n - Internationalization service.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
transactionsByVendorRepository: TransactionsByVendorRepository,
|
||||||
|
filter: ITransactionsByVendorsFilter,
|
||||||
|
i18n: I18nService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.repository = transactionsByVendorRepository;
|
||||||
|
this.filter = filter;
|
||||||
|
this.numberFormat = this.filter.numberFormat;
|
||||||
|
this.i18n = i18n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the vendor transactions from the given vendor id and opening balance.
|
||||||
|
* @param {number} vendorId - Vendor id.
|
||||||
|
* @param {number} openingBalance - Opening balance amount.
|
||||||
|
* @returns {ITransactionsByVendorsTransaction[]}
|
||||||
|
*/
|
||||||
|
private vendorTransactions(
|
||||||
|
vendorId: number,
|
||||||
|
openingBalance: number,
|
||||||
|
): ITransactionsByVendorsTransaction[] {
|
||||||
|
const openingBalanceLedger = this.repository.journal
|
||||||
|
.whereContactId(vendorId)
|
||||||
|
.whereFromDate(this.filter.fromDate)
|
||||||
|
.whereToDate(this.filter.toDate);
|
||||||
|
|
||||||
|
const openingEntries = openingBalanceLedger.getEntries();
|
||||||
|
|
||||||
|
return R.compose(
|
||||||
|
R.curry(this.contactTransactionRunningBalance)(openingBalance, 'credit'),
|
||||||
|
R.map(this.contactTransactionMapper.bind(this)),
|
||||||
|
).bind(this)(openingEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor section mapper.
|
||||||
|
* @param {IVendor} vendor
|
||||||
|
* @returns {ITransactionsByVendorsVendor}
|
||||||
|
*/
|
||||||
|
private vendorMapper(
|
||||||
|
vendor: ModelObject<Vendor>,
|
||||||
|
): ITransactionsByVendorsVendor {
|
||||||
|
const openingBalance = this.getContactOpeningBalance(vendor.id);
|
||||||
|
const transactions = this.vendorTransactions(vendor.id, openingBalance);
|
||||||
|
const closingBalance = this.getVendorClosingBalance(
|
||||||
|
transactions,
|
||||||
|
openingBalance,
|
||||||
|
);
|
||||||
|
const currencyCode = this.baseCurrency;
|
||||||
|
|
||||||
|
return {
|
||||||
|
vendorName: vendor.displayName,
|
||||||
|
openingBalance: this.getTotalAmountMeta(openingBalance, currencyCode),
|
||||||
|
closingBalance: this.getTotalAmountMeta(closingBalance, currencyCode),
|
||||||
|
transactions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the vendor closing balance from the given customer transactions.
|
||||||
|
* @param {ITransactionsByContactsTransaction[]} customerTransactions
|
||||||
|
* @param {number} openingBalance
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private getVendorClosingBalance(
|
||||||
|
vendorTransactions: ITransactionsByVendorsTransaction[],
|
||||||
|
openingBalance: number,
|
||||||
|
) {
|
||||||
|
return this.getContactClosingBalance(
|
||||||
|
vendorTransactions,
|
||||||
|
VENDOR_NORMAL,
|
||||||
|
openingBalance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the vendors post filter is active.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
private isVendorsPostFilter = (): boolean => {
|
||||||
|
return isEmpty(this.filter.vendorsIds);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the vendors sections of the report.
|
||||||
|
* @param {IVendor[]} vendors
|
||||||
|
* @returns {ITransactionsByVendorsVendor[]}
|
||||||
|
*/
|
||||||
|
private vendorsMapper(
|
||||||
|
vendors: ModelObject<Vendor>[],
|
||||||
|
): ITransactionsByVendorsVendor[] {
|
||||||
|
return R.compose(
|
||||||
|
R.when(this.isVendorsPostFilter, this.contactsFilter),
|
||||||
|
R.map(this.vendorMapper.bind(this)),
|
||||||
|
).bind(this)(vendors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the report data.
|
||||||
|
* @returns {ITransactionsByVendorsData}
|
||||||
|
*/
|
||||||
|
public reportData(): ITransactionsByVendorsData {
|
||||||
|
return this.vendorsMapper(this.repository.vendors);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
import { IFinancialSheetCommonMeta } from '../../types/Report.types';
|
||||||
|
import { IFinancialTable } from '../../types/Table.types';
|
||||||
|
import {
|
||||||
|
ITransactionsByContactsAmount,
|
||||||
|
ITransactionsByContactsTransaction,
|
||||||
|
ITransactionsByContactsFilter,
|
||||||
|
} from '../TransactionsByContact/TransactionsByContact.types';
|
||||||
|
|
||||||
|
export interface ITransactionsByVendorsAmount
|
||||||
|
extends ITransactionsByContactsAmount {}
|
||||||
|
|
||||||
|
export interface ITransactionsByVendorsTransaction
|
||||||
|
extends ITransactionsByContactsTransaction {}
|
||||||
|
|
||||||
|
export interface ITransactionsByVendorsVendor {
|
||||||
|
vendorName: string;
|
||||||
|
openingBalance: ITransactionsByVendorsAmount;
|
||||||
|
closingBalance: ITransactionsByVendorsAmount;
|
||||||
|
transactions: ITransactionsByVendorsTransaction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITransactionsByVendorsFilter
|
||||||
|
extends ITransactionsByContactsFilter {
|
||||||
|
vendorsIds: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ITransactionsByVendorsData = ITransactionsByVendorsVendor[];
|
||||||
|
|
||||||
|
export interface ITransactionsByVendorsStatement {
|
||||||
|
data: ITransactionsByVendorsData;
|
||||||
|
query: ITransactionsByVendorsFilter;
|
||||||
|
meta: ITransactionsByVendorMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITransactionsByVendorsService {
|
||||||
|
transactionsByVendors(
|
||||||
|
tenantId: number,
|
||||||
|
filter: ITransactionsByVendorsFilter
|
||||||
|
): Promise<ITransactionsByVendorsStatement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITransactionsByVendorTable extends IFinancialTable {
|
||||||
|
query: ITransactionsByVendorsFilter;
|
||||||
|
meta: ITransactionsByVendorMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITransactionsByVendorMeta extends IFinancialSheetCommonMeta {
|
||||||
|
formattedFromDate: string;
|
||||||
|
formattedToDate: string;
|
||||||
|
formattedDateRange: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import {
|
||||||
|
ITransactionsByVendorTable,
|
||||||
|
ITransactionsByVendorsFilter,
|
||||||
|
ITransactionsByVendorsStatement,
|
||||||
|
} from './TransactionsByVendor.types';
|
||||||
|
import { TransactionsByVendorExportInjectable } from './TransactionsByVendorExportInjectable';
|
||||||
|
import { TransactionsByVendorTableInjectable } from './TransactionsByVendorTableInjectable';
|
||||||
|
import { TransactionsByVendorsInjectable } from './TransactionsByVendorInjectable';
|
||||||
|
import { TransactionsByVendorsPdf } from './TransactionsByVendorPdf';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByVendorApplication {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionsByVendorTable: TransactionsByVendorTableInjectable,
|
||||||
|
private readonly transactionsByVendorExport: TransactionsByVendorExportInjectable,
|
||||||
|
private readonly transactionsByVendorSheet: TransactionsByVendorsInjectable,
|
||||||
|
private readonly transactionsByVendorPdf: TransactionsByVendorsPdf,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by vendor in sheet format.
|
||||||
|
* @param {ITransactionsByVendorsFilter} query - The filter query.
|
||||||
|
* @returns {Promise<ITransactionsByVendorsStatement>}
|
||||||
|
*/
|
||||||
|
public sheet(
|
||||||
|
query: ITransactionsByVendorsFilter
|
||||||
|
): Promise<ITransactionsByVendorsStatement> {
|
||||||
|
return this.transactionsByVendorSheet.transactionsByVendors(
|
||||||
|
query
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by vendor in table format.
|
||||||
|
* @param {ITransactionsByVendorsFilter} query
|
||||||
|
* @returns {Promise<ITransactionsByVendorTable>}
|
||||||
|
*/
|
||||||
|
public table(
|
||||||
|
query: ITransactionsByVendorsFilter
|
||||||
|
): Promise<ITransactionsByVendorTable> {
|
||||||
|
return this.transactionsByVendorTable.table(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by vendor in CSV format.
|
||||||
|
* @param {ITransactionsByVendorsFilter} query
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
public csv(
|
||||||
|
query: ITransactionsByVendorsFilter
|
||||||
|
): Promise<string> {
|
||||||
|
return this.transactionsByVendorExport.csv(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by vendor in XLSX format.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {ITransactionsByVendorsFilter} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public xlsx(
|
||||||
|
query: ITransactionsByVendorsFilter
|
||||||
|
): Promise<Buffer> {
|
||||||
|
return this.transactionsByVendorExport.xlsx(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by vendor in PDF format.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {ITransactionsByVendorsFilter} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public pdf(query: ITransactionsByVendorsFilter) {
|
||||||
|
return this.transactionsByVendorPdf.pdf(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ITransactionsByVendorsFilter } from './TransactionsByVendor.types';
|
||||||
|
import { TransactionsByVendorTableInjectable } from './TransactionsByVendorTableInjectable';
|
||||||
|
import { TableSheet } from '../../common/TableSheet';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByVendorExportInjectable {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionsByVendorTable: TransactionsByVendorTableInjectable,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the cashflow sheet in XLSX format.
|
||||||
|
* @param {ITransactionsByVendorsFilter} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public async xlsx(
|
||||||
|
query: ITransactionsByVendorsFilter
|
||||||
|
): Promise<Buffer> {
|
||||||
|
const table = await this.transactionsByVendorTable.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 {ICashFlowStatementQuery} query
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
public async csv(
|
||||||
|
query: ITransactionsByVendorsFilter
|
||||||
|
): Promise<string> {
|
||||||
|
const table = await this.transactionsByVendorTable.table(query);
|
||||||
|
|
||||||
|
const tableSheet = new TableSheet(table.table);
|
||||||
|
const tableCsv = tableSheet.convertToCSV();
|
||||||
|
|
||||||
|
return tableCsv;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import {
|
||||||
|
ITransactionsByVendorsFilter,
|
||||||
|
ITransactionsByVendorsStatement,
|
||||||
|
} from './TransactionsByVendor.types';
|
||||||
|
import { TransactionsByVendor } from './TransactionsByVendor';
|
||||||
|
import { TransactionsByVendorRepository } from './TransactionsByVendorRepository';
|
||||||
|
import { TransactionsByVendorMeta } from './TransactionsByVendorMeta';
|
||||||
|
import { getTransactionsByVendorDefaultQuery } from './utils';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByVendorsInjectable {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionsByVendorRepository: TransactionsByVendorRepository,
|
||||||
|
private readonly transactionsByVendorMeta: TransactionsByVendorMeta,
|
||||||
|
private readonly eventPublisher: EventEmitter2,
|
||||||
|
private readonly i18n: I18nService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve transactions by by the customers.
|
||||||
|
* @param {ITransactionsByVendorsFilter} query - Transactions by vendors filter.
|
||||||
|
* @return {Promise<ITransactionsByVendorsStatement>}
|
||||||
|
*/
|
||||||
|
public async transactionsByVendors(
|
||||||
|
query: ITransactionsByVendorsFilter,
|
||||||
|
): Promise<ITransactionsByVendorsStatement> {
|
||||||
|
const filter = { ...getTransactionsByVendorDefaultQuery(), ...query };
|
||||||
|
|
||||||
|
// Transactions by customers data mapper.
|
||||||
|
const reportInstance = new TransactionsByVendor(
|
||||||
|
this.transactionsByVendorRepository,
|
||||||
|
filter,
|
||||||
|
this.i18n,
|
||||||
|
);
|
||||||
|
const meta = await this.transactionsByVendorMeta.meta(filter);
|
||||||
|
|
||||||
|
// Triggers `onVendorTransactionsViewed` event.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.reports.onVendorTransactionsViewed,
|
||||||
|
{ query },
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: reportInstance.reportData(),
|
||||||
|
query: filter,
|
||||||
|
meta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import * as moment from 'moment';
|
||||||
|
import {
|
||||||
|
ITransactionsByVendorMeta,
|
||||||
|
ITransactionsByVendorsFilter,
|
||||||
|
} from './TransactionsByVendor.types';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { FinancialSheetMeta } from '../../common/FinancialSheetMeta';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByVendorMeta {
|
||||||
|
constructor(
|
||||||
|
private readonly financialSheetMeta: FinancialSheetMeta,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by vendor meta.
|
||||||
|
* @returns {Promise<ITransactionsByVendorMeta>}
|
||||||
|
*/
|
||||||
|
public async meta(
|
||||||
|
query: ITransactionsByVendorsFilter
|
||||||
|
): Promise<ITransactionsByVendorMeta> {
|
||||||
|
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 = 'Transactions By Vendor';
|
||||||
|
|
||||||
|
return {
|
||||||
|
...commonMeta,
|
||||||
|
sheetName,
|
||||||
|
formattedFromDate,
|
||||||
|
formattedToDate,
|
||||||
|
formattedDateRange,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { TableSheetPdf } from '../../common/TableSheetPdf';
|
||||||
|
import { ITransactionsByVendorsFilter } from './TransactionsByVendor.types';
|
||||||
|
import { TransactionsByVendorTableInjectable } from './TransactionsByVendorTableInjectable';
|
||||||
|
import { HtmlTableCustomCss } from './constants';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByVendorsPdf {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionsByVendorTable: TransactionsByVendorTableInjectable,
|
||||||
|
private readonly tableSheetPdf: TableSheetPdf,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given balance sheet table to pdf.
|
||||||
|
* @param {IBalanceSheetQuery} query - Balance sheet query.
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public async pdf(query: ITransactionsByVendorsFilter): Promise<Buffer> {
|
||||||
|
const table = await this.transactionsByVendorTable.table(query);
|
||||||
|
|
||||||
|
return this.tableSheetPdf.convertToPdf(
|
||||||
|
table.table,
|
||||||
|
table.meta.sheetName,
|
||||||
|
table.meta.formattedDateRange,
|
||||||
|
HtmlTableCustomCss,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { isEmpty, map } from 'lodash';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
||||||
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
|
import { Vendor } from '@/modules/Vendors/models/Vendor';
|
||||||
|
import { ACCOUNT_TYPE } from '@/constants/accounts';
|
||||||
|
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||||
|
import { TransactionsByContactRepository } from '../TransactionsByContact/TransactionsByContactRepository';
|
||||||
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
|
||||||
|
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||||
|
import { ModelObject } from 'objection';
|
||||||
|
import { ITransactionsByVendorsFilter } from './TransactionsByVendor.types';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByVendorRepository extends TransactionsByContactRepository {
|
||||||
|
@Inject(TenancyContext)
|
||||||
|
public readonly tenancyContext: TenancyContext;
|
||||||
|
|
||||||
|
@Inject(AccountRepository)
|
||||||
|
public readonly accountRepository: AccountRepository;
|
||||||
|
|
||||||
|
@Inject(Vendor.name)
|
||||||
|
public readonly vendorModel: typeof Vendor;
|
||||||
|
|
||||||
|
@Inject(Account.name)
|
||||||
|
public readonly accountModel: typeof Account;
|
||||||
|
|
||||||
|
@Inject(AccountTransaction.name)
|
||||||
|
public readonly accountTransactionModel: typeof AccountTransaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ledger.
|
||||||
|
* @param {Ledger} ledger
|
||||||
|
*/
|
||||||
|
public ledger: Ledger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendors.
|
||||||
|
* @param {ModelObject<Vendor>[]} vendors
|
||||||
|
*/
|
||||||
|
public vendors: ModelObject<Vendor>[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts graph.
|
||||||
|
* @param {any} accountsGraph
|
||||||
|
*/
|
||||||
|
public accountsGraph: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base currency.
|
||||||
|
* @param {string} baseCurrency
|
||||||
|
*/
|
||||||
|
public baseCurrency: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report filter.
|
||||||
|
* @param {ITransactionsByVendorsFilter} filter
|
||||||
|
*/
|
||||||
|
public filter: ITransactionsByVendorsFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report entries.
|
||||||
|
* @param {ILedgerEntry[]} reportEntries
|
||||||
|
*/
|
||||||
|
public reportEntries: ILedgerEntry[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Journal.
|
||||||
|
* @param {Ledger} journal
|
||||||
|
*/
|
||||||
|
public journal: Ledger;
|
||||||
|
|
||||||
|
async asyncInit() {
|
||||||
|
await this.initBaseCurrency();
|
||||||
|
await this.initVendors();
|
||||||
|
await this.initAccountsGraph();
|
||||||
|
await this.initPeriodEntries();
|
||||||
|
await this.initLedger();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the base currency.
|
||||||
|
*/
|
||||||
|
async initBaseCurrency() {
|
||||||
|
const tenantMetadata = await this.tenancyContext.getTenantMetadata();
|
||||||
|
this.baseCurrency = tenantMetadata.baseCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the vendors.
|
||||||
|
*/
|
||||||
|
async initVendors() {
|
||||||
|
const vendors = await this.getVendors(this.filter.vendorsIds);
|
||||||
|
this.vendors = vendors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the accounts graph.
|
||||||
|
*/
|
||||||
|
async initAccountsGraph() {
|
||||||
|
this.accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the report entries.
|
||||||
|
*/
|
||||||
|
async initPeriodEntries() {
|
||||||
|
this.reportEntries = await this.getReportEntries(
|
||||||
|
this.filter.fromDate,
|
||||||
|
this.filter.toDate,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async initLedger() {
|
||||||
|
this.journal = new Ledger(this.reportEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the vendors opening balance transactions.
|
||||||
|
* @param {Date} openingDate - The opening date.
|
||||||
|
* @param {number[]} customersIds - The customers ids.
|
||||||
|
* @returns {Promise<ILedgerEntry[]>}
|
||||||
|
*/
|
||||||
|
public async getVendorsOpeningBalanceEntries(
|
||||||
|
openingDate: Date,
|
||||||
|
customersIds?: number[],
|
||||||
|
): Promise<ILedgerEntry[]> {
|
||||||
|
const openingTransactions = await this.getVendorsOpeningBalance(
|
||||||
|
openingDate,
|
||||||
|
customersIds,
|
||||||
|
);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return R.compose(
|
||||||
|
R.map(R.assoc('date', openingDate)),
|
||||||
|
R.map(R.assoc('accountNormal', 'credit')),
|
||||||
|
)(openingTransactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the vendors period transactions.
|
||||||
|
* @param {Date|string} openingDate
|
||||||
|
* @param {number[]} customersIds
|
||||||
|
*/
|
||||||
|
public async getVendorsPeriodEntries(
|
||||||
|
fromDate: moment.MomentInput,
|
||||||
|
toDate: moment.MomentInput,
|
||||||
|
): Promise<ILedgerEntry[]> {
|
||||||
|
const transactions = await this.getVendorsPeriodTransactions(
|
||||||
|
fromDate,
|
||||||
|
toDate,
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
return R.compose(
|
||||||
|
R.map(R.assoc('accountNormal', 'credit')),
|
||||||
|
R.map((trans) => ({
|
||||||
|
// @ts-ignore
|
||||||
|
...trans,
|
||||||
|
// @ts-ignore
|
||||||
|
referenceTypeFormatted: trans.referenceTypeFormatted,
|
||||||
|
})),
|
||||||
|
)(transactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the report ledger entries from repository.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {Date} fromDate
|
||||||
|
* @param {Date} toDate
|
||||||
|
* @returns {Promise<ILedgerEntry[]>}
|
||||||
|
*/
|
||||||
|
public async getReportEntries(
|
||||||
|
fromDate: moment.MomentInput,
|
||||||
|
toDate: moment.MomentInput,
|
||||||
|
): Promise<ILedgerEntry[]> {
|
||||||
|
const openingBalanceDate = moment(fromDate).subtract(1, 'days').toDate();
|
||||||
|
|
||||||
|
return [
|
||||||
|
...(await this.getVendorsOpeningBalanceEntries(openingBalanceDate)),
|
||||||
|
...(await this.getVendorsPeriodEntries(fromDate, toDate)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the report vendors.
|
||||||
|
* @param {number[]} vendorsIds - The vendors IDs.
|
||||||
|
* @returns {Promise<IVendor[]>}
|
||||||
|
*/
|
||||||
|
public async getVendors(vendorsIds?: number[]): Promise<Vendor[]> {
|
||||||
|
return await this.vendorModel.query().onBuild((q) => {
|
||||||
|
q.orderBy('displayName');
|
||||||
|
|
||||||
|
if (!isEmpty(vendorsIds)) {
|
||||||
|
q.whereIn('id', vendorsIds);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the accounts receivable.
|
||||||
|
* @returns {Promise<IAccount[]>}
|
||||||
|
*/
|
||||||
|
public async getPayableAccounts(): Promise<Account[]> {
|
||||||
|
const accounts = await this.accountModel
|
||||||
|
.query()
|
||||||
|
.where('accountType', ACCOUNT_TYPE.ACCOUNTS_PAYABLE);
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the customers opening balance transactions.
|
||||||
|
* @param {Date} openingDate - The opening date.
|
||||||
|
* @param {number[]} customersIds - The customers IDs.
|
||||||
|
* @returns {Promise<AccountTransaction[]>}
|
||||||
|
*/
|
||||||
|
public async getVendorsOpeningBalance(
|
||||||
|
openingDate: Date,
|
||||||
|
customersIds?: number[],
|
||||||
|
): Promise<AccountTransaction[]> {
|
||||||
|
const payableAccounts = await this.getPayableAccounts();
|
||||||
|
const payableAccountsIds = map(payableAccounts, 'id');
|
||||||
|
|
||||||
|
const openingTransactions = await this.accountTransactionModel
|
||||||
|
.query()
|
||||||
|
.modify(
|
||||||
|
'contactsOpeningBalance',
|
||||||
|
openingDate,
|
||||||
|
payableAccountsIds,
|
||||||
|
customersIds,
|
||||||
|
);
|
||||||
|
return openingTransactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve vendors periods transactions.
|
||||||
|
* @param {Date} fromDate - The from date.
|
||||||
|
* @param {Date} toDate - The to date.
|
||||||
|
* @returns {Promise<AccountTransaction[]>}
|
||||||
|
*/
|
||||||
|
public async getVendorsPeriodTransactions(
|
||||||
|
fromDate: moment.MomentInput,
|
||||||
|
toDate: moment.MomentInput,
|
||||||
|
): Promise<AccountTransaction[]> {
|
||||||
|
const receivableAccounts = await this.getPayableAccounts();
|
||||||
|
const receivableAccountsIds = map(receivableAccounts, 'id');
|
||||||
|
|
||||||
|
const transactions = await this.accountTransactionModel
|
||||||
|
.query()
|
||||||
|
.onBuild((query) => {
|
||||||
|
// Filter by date.
|
||||||
|
query.modify('filterDateRange', fromDate, toDate);
|
||||||
|
|
||||||
|
// Filter by customers.
|
||||||
|
query.whereNot('contactId', null);
|
||||||
|
|
||||||
|
// Filter by accounts.
|
||||||
|
query.whereIn('accountId', receivableAccountsIds);
|
||||||
|
});
|
||||||
|
return transactions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { ITransactionsByVendorsVendor } from './TransactionsByVendor.types';
|
||||||
|
import { TransactionsByContactsTableRows } from '../TransactionsByContact/TransactionsByContactTableRows';
|
||||||
|
import { tableRowMapper } from '../../utils/Table.utils';
|
||||||
|
import { ITableRow, ITableColumn } from '../../types/Table.types';
|
||||||
|
|
||||||
|
enum ROW_TYPE {
|
||||||
|
OPENING_BALANCE = 'OPENING_BALANCE',
|
||||||
|
CLOSING_BALANCE = 'CLOSING_BALANCE',
|
||||||
|
TRANSACTION = 'TRANSACTION',
|
||||||
|
VENDOR = 'VENDOR',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TransactionsByVendorsTable extends TransactionsByContactsTableRows {
|
||||||
|
private vendorsTransactions: ITransactionsByVendorsVendor[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param {ITransactionsByVendorsVendor[]} vendorsTransactions -
|
||||||
|
* @param {any} i18n
|
||||||
|
*/
|
||||||
|
constructor(vendorsTransactions: ITransactionsByVendorsVendor[], i18n) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.vendorsTransactions = vendorsTransactions;
|
||||||
|
this.i18n = i18n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the table row of vendor details.
|
||||||
|
* @param {ITransactionsByVendorsVendor} vendor -
|
||||||
|
* @returns {ITableRow[]}
|
||||||
|
*/
|
||||||
|
private vendorDetails = (vendor: ITransactionsByVendorsVendor) => {
|
||||||
|
const columns = [
|
||||||
|
{ key: 'vendorName', accessor: 'vendorName' },
|
||||||
|
...R.repeat({ key: 'empty', value: '' }, 5),
|
||||||
|
{
|
||||||
|
key: 'closingBalanceValue',
|
||||||
|
accessor: 'closingBalance.formattedAmount',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
...tableRowMapper(vendor, columns, { rowTypes: [ROW_TYPE.VENDOR] }),
|
||||||
|
children: R.pipe(
|
||||||
|
R.when(
|
||||||
|
R.always(vendor.transactions.length > 0),
|
||||||
|
R.pipe(
|
||||||
|
R.concat(this.contactTransactions(vendor)),
|
||||||
|
R.prepend(this.contactOpeningBalance(vendor)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
R.append(this.contactClosingBalance(vendor)),
|
||||||
|
)([]),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the table rows of the vendor section.
|
||||||
|
* @param {ITransactionsByVendorsVendor} vendor
|
||||||
|
* @returns {ITableRow[]}
|
||||||
|
*/
|
||||||
|
private vendorRowsMapper = (vendor: ITransactionsByVendorsVendor) => {
|
||||||
|
return R.pipe(this.vendorDetails)(vendor);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the table rows of transactions by vendors report.
|
||||||
|
* @param {ITransactionsByVendorsVendor[]} vendors
|
||||||
|
* @returns {ITableRow[]}
|
||||||
|
*/
|
||||||
|
public tableRows = (): ITableRow[] => {
|
||||||
|
return R.map(this.vendorRowsMapper)(this.vendorsTransactions);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the table columns of transactions by vendors report.
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
public tableColumns = (): ITableColumn[] => {
|
||||||
|
return [
|
||||||
|
{ key: 'vendor_name', label: 'Vendor name' },
|
||||||
|
{ key: 'account_name', label: 'Account Name' },
|
||||||
|
{ key: 'ref_type', label: 'Reference Type' },
|
||||||
|
{ key: 'transaction_type', label: 'Transaction Type' },
|
||||||
|
{ key: 'credit', label: 'Credit' },
|
||||||
|
{ key: 'debit', label: 'Debit' },
|
||||||
|
{ key: 'running_balance', label: 'Running Balance' },
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { TransactionsByVendorsTable } from './TransactionsByVendorTable';
|
||||||
|
import {
|
||||||
|
ITransactionsByVendorTable,
|
||||||
|
ITransactionsByVendorsFilter,
|
||||||
|
} from './TransactionsByVendor.types';
|
||||||
|
import { TransactionsByVendorsInjectable } from './TransactionsByVendorInjectable';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsByVendorTableInjectable {
|
||||||
|
constructor(
|
||||||
|
private readonly transactionsByVendor: TransactionsByVendorsInjectable,
|
||||||
|
private readonly i18n: I18nService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the transactions by vendor in table format.
|
||||||
|
* @param {ITransactionsByReferenceQuery} query - The filter query.
|
||||||
|
* @returns {Promise<ITransactionsByVendorTable>}
|
||||||
|
*/
|
||||||
|
public async table(
|
||||||
|
query: ITransactionsByVendorsFilter
|
||||||
|
): Promise<ITransactionsByVendorTable> {
|
||||||
|
const sheet = await this.transactionsByVendor.transactionsByVendors(
|
||||||
|
query
|
||||||
|
);
|
||||||
|
const table = new TransactionsByVendorsTable(sheet.data, this.i18n);
|
||||||
|
|
||||||
|
return {
|
||||||
|
table: {
|
||||||
|
rows: table.tableRows(),
|
||||||
|
columns: table.tableColumns(),
|
||||||
|
},
|
||||||
|
query,
|
||||||
|
meta: sheet.meta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
export const HtmlTableCustomCss = `
|
||||||
|
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,
|
||||||
|
table .column--running_balance,
|
||||||
|
table .cell--running_balance{
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
table tr.row-type--closing-balance td,
|
||||||
|
table tr.row-type--opening-balance td {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
table tr.row-type--vendor:not(:first-child) td {
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
export const getTransactionsByVendorDefaultQuery = () => {
|
||||||
|
return {
|
||||||
|
fromDate: moment().startOf('month').format('YYYY-MM-DD'),
|
||||||
|
toDate: moment().format('YYYY-MM-DD'),
|
||||||
|
numberFormat: {
|
||||||
|
precision: 2,
|
||||||
|
divideOn1000: false,
|
||||||
|
showZero: false,
|
||||||
|
formatMoney: 'total',
|
||||||
|
negativeFormat: 'mines',
|
||||||
|
},
|
||||||
|
comparison: {
|
||||||
|
percentageOfColumn: true,
|
||||||
|
},
|
||||||
|
noneZero: false,
|
||||||
|
noneTransactions: true,
|
||||||
|
vendorsIds: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { TrialBalanceSheetTableInjectable } from './TrialBalanceSheetTableInjectable';
|
||||||
|
import { TrialBalanceSheetPdfInjectable } from './TrialBalanceSheetPdfInjectsable';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { TableSheet } from '../../common/TableSheet';
|
||||||
|
import { ITrialBalanceSheetQuery } from './TrialBalanceSheet.types';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TrialBalanceExportInjectable {
|
||||||
|
constructor(
|
||||||
|
private readonly trialBalanceSheetTable: TrialBalanceSheetTableInjectable,
|
||||||
|
private readonly trialBalanceSheetPdf: TrialBalanceSheetPdfInjectable,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the trial balance sheet in XLSX format.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {ITrialBalanceSheetQuery} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public async xlsx(query: ITrialBalanceSheetQuery) {
|
||||||
|
const table = await this.trialBalanceSheetTable.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 {number} tenantId
|
||||||
|
* @param {ITrialBalanceSheetQuery} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public async csv(query: ITrialBalanceSheetQuery): Promise<string> {
|
||||||
|
const table = await this.trialBalanceSheetTable.table(query);
|
||||||
|
|
||||||
|
const tableSheet = new TableSheet(table.table);
|
||||||
|
const tableCsv = tableSheet.convertToCSV();
|
||||||
|
|
||||||
|
return tableCsv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the trial balance sheet in PDF format.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {ITrialBalanceSheetQuery} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public async pdf(query: ITrialBalanceSheetQuery): Promise<Buffer> {
|
||||||
|
return this.trialBalanceSheetPdf.pdf(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Headers,
|
||||||
|
Query,
|
||||||
|
Res,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { castArray } from 'lodash';
|
||||||
|
import { Response } from 'express';
|
||||||
|
import { ITrialBalanceSheetQuery } from './TrialBalanceSheet.types';
|
||||||
|
import { AcceptType } from '@/constants/accept-type';
|
||||||
|
import { TrialBalanceSheetApplication } from './TrialBalanceSheetApplication';
|
||||||
|
import { PublicRoute } from '@/modules/Auth/Jwt.guard';
|
||||||
|
|
||||||
|
@Controller('reports/trial-balance-sheet')
|
||||||
|
@ApiTags('reports')
|
||||||
|
@PublicRoute()
|
||||||
|
export class TrialBalanceSheetController {
|
||||||
|
constructor(
|
||||||
|
private readonly trialBalanceSheetApp: TrialBalanceSheetApplication,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Get trial balance sheet' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Trial balance sheet' })
|
||||||
|
async getTrialBalanceSheet(
|
||||||
|
@Query() query: ITrialBalanceSheetQuery,
|
||||||
|
@Res() res: Response,
|
||||||
|
@Headers('accept') acceptHeader: string,
|
||||||
|
) {
|
||||||
|
const filter = {
|
||||||
|
...query,
|
||||||
|
accountIds: castArray(query.accountIds),
|
||||||
|
};
|
||||||
|
// Retrieves in json table format.
|
||||||
|
if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
||||||
|
const { table, meta, query } = await this.trialBalanceSheetApp.table(
|
||||||
|
filter,
|
||||||
|
);
|
||||||
|
return res.status(200).send({ table, meta, query });
|
||||||
|
// Retrieves in xlsx format
|
||||||
|
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
||||||
|
const buffer = await this.trialBalanceSheetApp.xlsx(filter);
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||||
|
res.setHeader(
|
||||||
|
'Content-Type',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
);
|
||||||
|
return res.send(buffer);
|
||||||
|
// Retrieves in csv format.
|
||||||
|
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
||||||
|
const buffer = await this.trialBalanceSheetApp.csv(filter);
|
||||||
|
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
|
res.setHeader('Content-Type', 'text/csv');
|
||||||
|
|
||||||
|
return res.send(buffer);
|
||||||
|
// Retrieves in pdf format.
|
||||||
|
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
||||||
|
const pdfContent = await this.trialBalanceSheetApp.pdf(filter);
|
||||||
|
res.set({
|
||||||
|
'Content-Type': 'application/pdf',
|
||||||
|
'Content-Length': pdfContent.length,
|
||||||
|
});
|
||||||
|
res.send(pdfContent);
|
||||||
|
// Retrieves in json format.
|
||||||
|
} else {
|
||||||
|
const { data, query, meta } = await this.trialBalanceSheetApp.sheet(
|
||||||
|
filter,
|
||||||
|
);
|
||||||
|
return res.status(200).send({ data, query, meta });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TrialBalanceExportInjectable } from './TrialBalanceExportInjectable';
|
||||||
|
import { TrialBalanceSheetController } from './TrialBalanceSheet.controller';
|
||||||
|
import { TrialBalanceSheetApplication } from './TrialBalanceSheetApplication';
|
||||||
|
import { TrialBalanceSheetService } from './TrialBalanceSheetInjectable';
|
||||||
|
import { TrialBalanceSheetTableInjectable } from './TrialBalanceSheetTableInjectable';
|
||||||
|
import { TrialBalanceSheetMeta } from './TrialBalanceSheetMeta';
|
||||||
|
import { TrialBalanceSheetRepository } from './TrialBalanceSheetRepository';
|
||||||
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
import { TrialBalanceSheetPdfInjectable } from './TrialBalanceSheetPdfInjectsable';
|
||||||
|
import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module';
|
||||||
|
import { AccountsModule } from '@/modules/Accounts/Accounts.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [FinancialSheetCommonModule, AccountsModule],
|
||||||
|
providers: [
|
||||||
|
TrialBalanceSheetApplication,
|
||||||
|
TrialBalanceSheetService,
|
||||||
|
TrialBalanceSheetTableInjectable,
|
||||||
|
TrialBalanceExportInjectable,
|
||||||
|
TrialBalanceSheetMeta,
|
||||||
|
TrialBalanceSheetRepository,
|
||||||
|
TenancyContext,
|
||||||
|
TrialBalanceSheetPdfInjectable
|
||||||
|
],
|
||||||
|
controllers: [TrialBalanceSheetController],
|
||||||
|
})
|
||||||
|
export class TrialBalanceSheetModule {}
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
import { sumBy } from 'lodash';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import {
|
||||||
|
ITrialBalanceSheetQuery,
|
||||||
|
ITrialBalanceAccount,
|
||||||
|
ITrialBalanceTotal,
|
||||||
|
ITrialBalanceSheetData,
|
||||||
|
} from './TrialBalanceSheet.types';
|
||||||
|
import { TrialBalanceSheetRepository } from './TrialBalanceSheetRepository';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
|
import { allPassedConditionsPass } from '@/utils/all-conditions-passed';
|
||||||
|
import { ModelObject } from 'objection';
|
||||||
|
import { flatToNestedArray } from '@/utils/flat-to-nested-array';
|
||||||
|
|
||||||
|
export class TrialBalanceSheet extends FinancialSheet {
|
||||||
|
/**
|
||||||
|
* Trial balance sheet query.
|
||||||
|
* @param {ITrialBalanceSheetQuery} query
|
||||||
|
*/
|
||||||
|
public query: ITrialBalanceSheetQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trial balance sheet repository.
|
||||||
|
* @param {TrialBalanceSheetRepository}
|
||||||
|
*/
|
||||||
|
public repository: TrialBalanceSheetRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organization base currency.
|
||||||
|
* @param {string}
|
||||||
|
*/
|
||||||
|
public baseCurrency: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {ITrialBalanceSheetQuery} query
|
||||||
|
* @param {IAccount[]} accounts
|
||||||
|
* @param journalFinancial
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
query: ITrialBalanceSheetQuery,
|
||||||
|
repository: TrialBalanceSheetRepository,
|
||||||
|
baseCurrency: string
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.query = query;
|
||||||
|
this.repository = repository;
|
||||||
|
this.numberFormat = this.query.numberFormat;
|
||||||
|
this.baseCurrency = baseCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the closing credit of the given account.
|
||||||
|
* @param {number} accountId
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public getClosingAccountCredit(accountId: number) {
|
||||||
|
const depsAccountsIds =
|
||||||
|
this.repository.accountsDepGraph.dependenciesOf(accountId);
|
||||||
|
|
||||||
|
return this.repository.totalAccountsLedger
|
||||||
|
.whereAccountsIds([accountId, ...depsAccountsIds])
|
||||||
|
.getClosingCredit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the closing debit of the given account.
|
||||||
|
* @param {number} accountId
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public getClosingAccountDebit(accountId: number) {
|
||||||
|
const depsAccountsIds =
|
||||||
|
this.repository.accountsDepGraph.dependenciesOf(accountId);
|
||||||
|
|
||||||
|
return this.repository.totalAccountsLedger
|
||||||
|
.whereAccountsIds([accountId, ...depsAccountsIds])
|
||||||
|
.getClosingDebit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the closing total of the given account.
|
||||||
|
* @param {number} accountId
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public getClosingAccountTotal(accountId: number) {
|
||||||
|
const credit = this.getClosingAccountCredit(accountId);
|
||||||
|
const debit = this.getClosingAccountDebit(accountId);
|
||||||
|
|
||||||
|
return debit - credit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account mapper.
|
||||||
|
* @param {IAccount} account
|
||||||
|
* @return {ITrialBalanceAccount}
|
||||||
|
*/
|
||||||
|
private accountTransformer = (
|
||||||
|
account: Account
|
||||||
|
): ITrialBalanceAccount => {
|
||||||
|
const debit = this.getClosingAccountDebit(account.id);
|
||||||
|
const credit = this.getClosingAccountCredit(account.id);
|
||||||
|
const balance = this.getClosingAccountTotal(account.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: account.id,
|
||||||
|
parentAccountId: account.parentAccountId,
|
||||||
|
name: account.name,
|
||||||
|
formattedName: account.code
|
||||||
|
? `${account.name} - ${account.code}`
|
||||||
|
: `${account.name}`,
|
||||||
|
code: account.code,
|
||||||
|
accountNormal: account.accountNormal,
|
||||||
|
|
||||||
|
credit,
|
||||||
|
debit,
|
||||||
|
balance,
|
||||||
|
currencyCode: this.baseCurrency,
|
||||||
|
|
||||||
|
formattedCredit: this.formatNumber(credit),
|
||||||
|
formattedDebit: this.formatNumber(debit),
|
||||||
|
formattedBalance: this.formatNumber(balance),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters trial balance sheet accounts nodes based on the given report query.
|
||||||
|
* @param {ITrialBalanceAccount} accountNode
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
private accountFilter = (accountNode: ITrialBalanceAccount): boolean => {
|
||||||
|
const { noneTransactions, noneZero, onlyActive } = this.query;
|
||||||
|
|
||||||
|
// Conditions pair filter detarminer.
|
||||||
|
const condsPairFilters = [
|
||||||
|
[noneTransactions, this.filterNoneTransactions],
|
||||||
|
[noneZero, this.filterNoneZero],
|
||||||
|
[onlyActive, this.filterActiveOnly],
|
||||||
|
];
|
||||||
|
return allPassedConditionsPass(condsPairFilters)(accountNode);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fitlers the accounts nodes.
|
||||||
|
* @param {ITrialBalanceAccount[]} accountsNodes
|
||||||
|
* @returns {ITrialBalanceAccount[]}
|
||||||
|
*/
|
||||||
|
private accountsFilter = (
|
||||||
|
accountsNodes: ITrialBalanceAccount[]
|
||||||
|
): ITrialBalanceAccount[] => {
|
||||||
|
return accountsNodes.filter(this.accountFilter);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mappes the given account object to trial balance account node.
|
||||||
|
* @param {IAccount[]} accountsNodes
|
||||||
|
* @returns {ITrialBalanceAccount[]}
|
||||||
|
*/
|
||||||
|
private accountsMapper = (
|
||||||
|
accountsNodes: ModelObject<Account>[]
|
||||||
|
): ITrialBalanceAccount[] => {
|
||||||
|
return accountsNodes.map(this.accountTransformer);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the given account node is not none transactions.
|
||||||
|
* @param {ITrialBalanceAccount} accountNode
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
private filterNoneTransactions = (
|
||||||
|
accountNode: ITrialBalanceAccount
|
||||||
|
): boolean => {
|
||||||
|
return false === this.repository.totalAccountsLedger.isEmpty();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the given account none zero.
|
||||||
|
* @param {ITrialBalanceAccount} accountNode
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
private filterNoneZero = (accountNode: ITrialBalanceAccount): boolean => {
|
||||||
|
return accountNode.balance !== 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the given account is active.
|
||||||
|
* @param {ITrialBalanceAccount} accountNode
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
private filterActiveOnly = (accountNode: ITrialBalanceAccount): boolean => {
|
||||||
|
return accountNode.credit !== 0 || accountNode.debit !== 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the flatten nodes to nested nodes.
|
||||||
|
* @param {ITrialBalanceAccount[]} flattenAccounts
|
||||||
|
* @returns {ITrialBalanceAccount[]}
|
||||||
|
*/
|
||||||
|
private nestedAccountsNode = (
|
||||||
|
flattenAccounts: ITrialBalanceAccount[]
|
||||||
|
): ITrialBalanceAccount[] => {
|
||||||
|
return flatToNestedArray(flattenAccounts, {
|
||||||
|
id: 'id',
|
||||||
|
parentId: 'parentAccountId',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve trial balance total section.
|
||||||
|
* @param {ITrialBalanceAccount[]} accountsBalances
|
||||||
|
* @return {ITrialBalanceTotal}
|
||||||
|
*/
|
||||||
|
private tatalSection(
|
||||||
|
accountsBalances: ITrialBalanceAccount[]
|
||||||
|
): ITrialBalanceTotal {
|
||||||
|
const credit = sumBy(accountsBalances, 'credit');
|
||||||
|
const debit = sumBy(accountsBalances, 'debit');
|
||||||
|
const balance = sumBy(accountsBalances, 'balance');
|
||||||
|
const currencyCode = this.baseCurrency;
|
||||||
|
|
||||||
|
return {
|
||||||
|
credit,
|
||||||
|
debit,
|
||||||
|
balance,
|
||||||
|
currencyCode,
|
||||||
|
formattedCredit: this.formatTotalNumber(credit),
|
||||||
|
formattedDebit: this.formatTotalNumber(debit),
|
||||||
|
formattedBalance: this.formatTotalNumber(balance),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts section of trial balance report.
|
||||||
|
* @param {IAccount[]} accounts
|
||||||
|
* @returns {ITrialBalanceAccount[]}
|
||||||
|
*/
|
||||||
|
private accountsSection(accounts: ModelObject<Account>[]) {
|
||||||
|
return R.compose(
|
||||||
|
this.nestedAccountsNode,
|
||||||
|
this.accountsFilter,
|
||||||
|
this.accountsMapper
|
||||||
|
)(accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve trial balance sheet statement data.
|
||||||
|
* Note: Retruns null in case there is no transactions between the given date periods.
|
||||||
|
*
|
||||||
|
* @return {ITrialBalanceSheetData}
|
||||||
|
*/
|
||||||
|
public reportData(): ITrialBalanceSheetData {
|
||||||
|
// Retrieve accounts nodes.
|
||||||
|
const accounts = this.accountsSection(this.repository.accounts);
|
||||||
|
|
||||||
|
// Retrieve account node.
|
||||||
|
const total = this.tatalSection(accounts);
|
||||||
|
|
||||||
|
return { accounts, total };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { IFinancialSheetCommonMeta, INumberFormatQuery } from "../../types/Report.types";
|
||||||
|
import { IFinancialTable } from "../../types/Table.types";
|
||||||
|
|
||||||
|
export interface ITrialBalanceSheetQuery {
|
||||||
|
fromDate: Date | string;
|
||||||
|
toDate: Date | string;
|
||||||
|
numberFormat: INumberFormatQuery;
|
||||||
|
basis: 'cash' | 'accrual';
|
||||||
|
noneZero: boolean;
|
||||||
|
noneTransactions: boolean;
|
||||||
|
onlyActive: boolean;
|
||||||
|
accountIds: number[];
|
||||||
|
branchesIds?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITrialBalanceTotal {
|
||||||
|
credit: number;
|
||||||
|
debit: number;
|
||||||
|
balance: number;
|
||||||
|
currencyCode: string;
|
||||||
|
|
||||||
|
formattedCredit: string;
|
||||||
|
formattedDebit: string;
|
||||||
|
formattedBalance: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITrialBalanceSheetMeta extends IFinancialSheetCommonMeta {
|
||||||
|
formattedFromDate: string;
|
||||||
|
formattedToDate: string;
|
||||||
|
formattedDateRange: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITrialBalanceAccount extends ITrialBalanceTotal {
|
||||||
|
id: number;
|
||||||
|
parentAccountId: number;
|
||||||
|
name: string;
|
||||||
|
formattedName: string;
|
||||||
|
code: string;
|
||||||
|
accountNormal: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ITrialBalanceSheetData = {
|
||||||
|
accounts: ITrialBalanceAccount[];
|
||||||
|
total: ITrialBalanceTotal;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ITrialBalanceStatement {
|
||||||
|
data: ITrialBalanceSheetData;
|
||||||
|
query: ITrialBalanceSheetQuery;
|
||||||
|
meta: ITrialBalanceSheetMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITrialBalanceSheetTable extends IFinancialTable {
|
||||||
|
meta: ITrialBalanceSheetMeta;
|
||||||
|
query: ITrialBalanceSheetQuery;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { TrialBalanceSheetTableInjectable } from './TrialBalanceSheetTableInjectable';
|
||||||
|
import { TrialBalanceExportInjectable } from './TrialBalanceExportInjectable';
|
||||||
|
import { ITrialBalanceSheetQuery, ITrialBalanceStatement } from './TrialBalanceSheet.types';
|
||||||
|
import { TrialBalanceSheetService } from './TrialBalanceSheetInjectable';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TrialBalanceSheetApplication {
|
||||||
|
constructor(
|
||||||
|
private readonly sheetService: TrialBalanceSheetService,
|
||||||
|
private readonly tablable: TrialBalanceSheetTableInjectable,
|
||||||
|
private readonly exportable: TrialBalanceExportInjectable,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the trial balance sheet.
|
||||||
|
* @param {ITrialBalanceSheetQuery} query
|
||||||
|
* @returns {Promise<ITrialBalanceStatement>}
|
||||||
|
*/
|
||||||
|
public sheet(
|
||||||
|
query: ITrialBalanceSheetQuery,
|
||||||
|
): Promise<ITrialBalanceStatement> {
|
||||||
|
return this.sheetService.trialBalanceSheet(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the trial balance sheet in table format.
|
||||||
|
* @param {ITrialBalanceSheetQuery} query
|
||||||
|
* @returns {Promise<ITrialBalanceSheetTable>}
|
||||||
|
*/
|
||||||
|
public table(query: ITrialBalanceSheetQuery) {
|
||||||
|
return this.tablable.table(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the trial balance sheet in CSV format.
|
||||||
|
* @param {ITrialBalanceSheetQuery} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public csv(query: ITrialBalanceSheetQuery) {
|
||||||
|
return this.exportable.csv(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the trial balance sheet in XLSX format.
|
||||||
|
* @param {ITrialBalanceSheetQuery} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public async xlsx(query: ITrialBalanceSheetQuery) {
|
||||||
|
return this.exportable.xlsx(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the trial balance sheet in pdf format.
|
||||||
|
* @param {ITrialBalanceSheetQuery} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public async pdf(query: ITrialBalanceSheetQuery) {
|
||||||
|
return this.exportable.pdf(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import {
|
||||||
|
ITrialBalanceSheetQuery,
|
||||||
|
ITrialBalanceStatement,
|
||||||
|
} from './TrialBalanceSheet.types';
|
||||||
|
import { TrialBalanceSheet } from './TrialBalanceSheet';
|
||||||
|
import { TrialBalanceSheetRepository } from './TrialBalanceSheetRepository';
|
||||||
|
import { TrialBalanceSheetMeta } from './TrialBalanceSheetMeta';
|
||||||
|
import { getTrialBalanceSheetDefaultQuery } from './_utils';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TrialBalanceSheetService {
|
||||||
|
constructor(
|
||||||
|
private readonly trialBalanceSheetMetaService: TrialBalanceSheetMeta,
|
||||||
|
private readonly eventPublisher: EventEmitter2,
|
||||||
|
private readonly trialBalanceSheetRepository: TrialBalanceSheetRepository,
|
||||||
|
private readonly tenancyContext: TenancyContext,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve trial balance sheet statement.
|
||||||
|
* @param {ITrialBalanceSheetQuery} query - Trial balance sheet query.
|
||||||
|
* @return {ITrialBalanceStatement}
|
||||||
|
*/
|
||||||
|
public async trialBalanceSheet(
|
||||||
|
query: ITrialBalanceSheetQuery,
|
||||||
|
): Promise<ITrialBalanceStatement> {
|
||||||
|
const filter = {
|
||||||
|
...getTrialBalanceSheetDefaultQuery(),
|
||||||
|
...query,
|
||||||
|
};
|
||||||
|
this.trialBalanceSheetRepository.setQuery(filter);
|
||||||
|
|
||||||
|
const tenantMetadata = await this.tenancyContext.getTenantMetadata();
|
||||||
|
|
||||||
|
// Loads the resources.
|
||||||
|
await this.trialBalanceSheetRepository.asyncInitialize();
|
||||||
|
|
||||||
|
// Trial balance report instance.
|
||||||
|
const trialBalanceInstance = new TrialBalanceSheet(
|
||||||
|
filter,
|
||||||
|
this.trialBalanceSheetRepository,
|
||||||
|
tenantMetadata.baseCurrency,
|
||||||
|
);
|
||||||
|
// Trial balance sheet data.
|
||||||
|
const trialBalanceSheetData = trialBalanceInstance.reportData();
|
||||||
|
|
||||||
|
// Trial balance sheet meta.
|
||||||
|
const meta = await this.trialBalanceSheetMetaService.meta(filter);
|
||||||
|
|
||||||
|
// Triggers `onTrialBalanceSheetViewed` event.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.reports.onTrialBalanceSheetView,
|
||||||
|
{
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: trialBalanceSheetData,
|
||||||
|
query: filter,
|
||||||
|
meta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import * as moment from 'moment';
|
||||||
|
import { ITrialBalanceSheetMeta, ITrialBalanceSheetQuery } from './TrialBalanceSheet.types';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { FinancialSheetMeta } from '../../common/FinancialSheetMeta';
|
||||||
|
@Injectable()
|
||||||
|
export class TrialBalanceSheetMeta {
|
||||||
|
constructor(private readonly financialSheetMeta: FinancialSheetMeta) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the trial balance sheet meta.
|
||||||
|
* @param {ITrialBalanceSheetQuery} query
|
||||||
|
* @returns {Promise<ITrialBalanceSheetMeta>}
|
||||||
|
*/
|
||||||
|
public async meta(
|
||||||
|
query: ITrialBalanceSheetQuery
|
||||||
|
): Promise<ITrialBalanceSheetMeta> {
|
||||||
|
const commonMeta = await this.financialSheetMeta.meta();
|
||||||
|
|
||||||
|
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD');
|
||||||
|
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD');
|
||||||
|
const formattedDateRange = `From ${formattedFromDate} to ${formattedToDate}`;
|
||||||
|
|
||||||
|
const sheetName = 'Trial Balance Sheet';
|
||||||
|
|
||||||
|
return {
|
||||||
|
...commonMeta,
|
||||||
|
sheetName,
|
||||||
|
formattedFromDate,
|
||||||
|
formattedToDate,
|
||||||
|
formattedDateRange,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { TableSheetPdf } from '../../common/TableSheetPdf';
|
||||||
|
import { ITrialBalanceSheetQuery } from './TrialBalanceSheet.types';
|
||||||
|
import { TrialBalanceSheetTableInjectable } from './TrialBalanceSheetTableInjectable';
|
||||||
|
import { HtmlTableCustomCss } from './_constants';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TrialBalanceSheetPdfInjectable {
|
||||||
|
constructor(
|
||||||
|
private readonly trialBalanceSheetTable: TrialBalanceSheetTableInjectable,
|
||||||
|
private readonly tableSheetPdf: TableSheetPdf,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given trial balance sheet table to pdf.
|
||||||
|
* @param {ITrialBalanceSheetQuery} query - Trial balance sheet query.
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public async pdf(query: ITrialBalanceSheetQuery): Promise<Buffer> {
|
||||||
|
const table = await this.trialBalanceSheetTable.table(query);
|
||||||
|
|
||||||
|
return this.tableSheetPdf.convertToPdf(
|
||||||
|
table.table,
|
||||||
|
table.meta.sheetName,
|
||||||
|
table.meta.formattedDateRange,
|
||||||
|
HtmlTableCustomCss,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
import { ModelObject } from 'objection';
|
||||||
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
|
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
||||||
|
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||||
|
import { ITrialBalanceSheetQuery } from './TrialBalanceSheet.types';
|
||||||
|
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||||
|
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
|
||||||
|
|
||||||
|
@Injectable({ scope: Scope.TRANSIENT })
|
||||||
|
export class TrialBalanceSheetRepository {
|
||||||
|
private query: ITrialBalanceSheetQuery;
|
||||||
|
|
||||||
|
@Inject(Account.name)
|
||||||
|
private accountModel: typeof Account;
|
||||||
|
|
||||||
|
@Inject(AccountTransaction.name)
|
||||||
|
private accountTransactionModel: typeof AccountTransaction;
|
||||||
|
|
||||||
|
@Inject(AccountRepository)
|
||||||
|
private accountRepository: AccountRepository;
|
||||||
|
|
||||||
|
public accountsDepGraph: any;
|
||||||
|
public accounts: Array<ModelObject<Account>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total closing accounts ledger.
|
||||||
|
* @param {Ledger}
|
||||||
|
*/
|
||||||
|
public totalAccountsLedger: Ledger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set query.
|
||||||
|
* @param {ITrialBalanceSheetQuery} query
|
||||||
|
*/
|
||||||
|
public setQuery(query: ITrialBalanceSheetQuery) {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async initialize.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public asyncInitialize = async () => {
|
||||||
|
await this.initAccounts();
|
||||||
|
await this.initAccountsClosingTotalLedger();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// # Accounts
|
||||||
|
// ----------------------------
|
||||||
|
/**
|
||||||
|
* Initialize accounts.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public initAccounts = async () => {
|
||||||
|
const accounts = await this.getAccounts();
|
||||||
|
const accountsDepGraph = await this.accountRepository.getDependencyGraph();
|
||||||
|
|
||||||
|
this.accountsDepGraph = accountsDepGraph;
|
||||||
|
this.accounts = accounts;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize all accounts closing total ledger.
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
public initAccountsClosingTotalLedger = async (): Promise<void> => {
|
||||||
|
const totalByAccounts = await this.closingAccountsTotal(this.query.toDate);
|
||||||
|
|
||||||
|
this.totalAccountsLedger = Ledger.fromTransactions(totalByAccounts);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts of the report.
|
||||||
|
* @return {Promise<IAccount[]>}
|
||||||
|
*/
|
||||||
|
private getAccounts = () => {
|
||||||
|
return this.accountModel.query();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the opening balance transactions of the report.
|
||||||
|
* @param {Date|string} openingDate -
|
||||||
|
*/
|
||||||
|
public closingAccountsTotal = async (openingDate: Date | string) => {
|
||||||
|
return this.accountTransactionModel.query().onBuild((query) => {
|
||||||
|
query.sum('credit as credit');
|
||||||
|
query.sum('debit as debit');
|
||||||
|
query.groupBy('accountId');
|
||||||
|
query.select(['accountId']);
|
||||||
|
|
||||||
|
query.modify('filterDateRange', null, openingDate);
|
||||||
|
query.withGraphFetched('account');
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.commonFilterBranchesQuery(query);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common branches filter query.
|
||||||
|
* @param {Knex.QueryBuilder} query
|
||||||
|
*/
|
||||||
|
private commonFilterBranchesQuery = (query: Knex.QueryBuilder) => {
|
||||||
|
if (!isEmpty(this.query.branchesIds)) {
|
||||||
|
// @ts-ignore
|
||||||
|
query.modify('filterByBranches', this.query.branchesIds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { FinancialTable } from '../../common/FinancialTable';
|
||||||
|
import {
|
||||||
|
ITrialBalanceAccount,
|
||||||
|
ITrialBalanceSheetData,
|
||||||
|
ITrialBalanceSheetQuery,
|
||||||
|
ITrialBalanceTotal,
|
||||||
|
} from './TrialBalanceSheet.types';
|
||||||
|
import {
|
||||||
|
ITableColumn,
|
||||||
|
ITableColumnAccessor,
|
||||||
|
ITableRow,
|
||||||
|
} from '../../types/Table.types';
|
||||||
|
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { tableRowMapper } from '../../utils/Table.utils';
|
||||||
|
import { IROW_TYPE } from './_constants';
|
||||||
|
|
||||||
|
export class TrialBalanceSheetTable extends R.compose(
|
||||||
|
FinancialTable,
|
||||||
|
FinancialSheetStructure,
|
||||||
|
)(FinancialSheet) {
|
||||||
|
/**
|
||||||
|
* Trial balance sheet data.
|
||||||
|
* @param {ITrialBalanceSheetData}
|
||||||
|
*/
|
||||||
|
public data: ITrialBalanceSheetData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trial balance sheet query.
|
||||||
|
* @param {ITrialBalanceSheetQuery}
|
||||||
|
*/
|
||||||
|
public query: ITrialBalanceSheetQuery;
|
||||||
|
|
||||||
|
public i18n: I18nService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param {IBalanceSheetStatementData} reportData -
|
||||||
|
* @param {ITrialBalanceSheetQuery} query -
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
data: ITrialBalanceSheetData,
|
||||||
|
query: ITrialBalanceSheetQuery,
|
||||||
|
i18n: I18nService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.data = data;
|
||||||
|
this.query = query;
|
||||||
|
this.i18n = i18n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the common columns for all report nodes.
|
||||||
|
* @param {ITableColumnAccessor[]}
|
||||||
|
*/
|
||||||
|
private commonColumnsAccessors = (): ITableColumnAccessor[] => {
|
||||||
|
return [
|
||||||
|
{ key: 'account', accessor: 'formattedName' },
|
||||||
|
{ key: 'debit', accessor: 'formattedDebit' },
|
||||||
|
{ key: 'credit', accessor: 'formattedCredit' },
|
||||||
|
{ key: 'total', accessor: 'formattedBalance' },
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the account node to table row.
|
||||||
|
* @param {ITrialBalanceAccount} node -
|
||||||
|
* @returns {ITableRow}
|
||||||
|
*/
|
||||||
|
private accountNodeTableRowsMapper = (
|
||||||
|
node: ITrialBalanceAccount,
|
||||||
|
): ITableRow => {
|
||||||
|
const columns = this.commonColumnsAccessors();
|
||||||
|
const meta = {
|
||||||
|
rowTypes: [IROW_TYPE.ACCOUNT],
|
||||||
|
id: node.id,
|
||||||
|
};
|
||||||
|
return tableRowMapper(node, columns, meta);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the total node to table row.
|
||||||
|
* @param {ITrialBalanceTotal} node -
|
||||||
|
* @returns {ITableRow}
|
||||||
|
*/
|
||||||
|
private totalNodeTableRowsMapper = (node: ITrialBalanceTotal): ITableRow => {
|
||||||
|
const columns = this.commonColumnsAccessors();
|
||||||
|
const meta = {
|
||||||
|
rowTypes: [IROW_TYPE.TOTAL],
|
||||||
|
id: 'total',
|
||||||
|
};
|
||||||
|
return tableRowMapper(node, columns, meta);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mappes the given report sections to table rows.
|
||||||
|
* @param {IBalanceSheetDataNode[]} nodes -
|
||||||
|
* @return {ITableRow}
|
||||||
|
*/
|
||||||
|
private accountsToTableRowsMap = (
|
||||||
|
nodes: ITrialBalanceAccount[],
|
||||||
|
): ITableRow[] => {
|
||||||
|
return this.mapNodesDeep(nodes, this.accountNodeTableRowsMapper);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the accounts table rows of the given report data.
|
||||||
|
* @returns {ITableRow[]}
|
||||||
|
*/
|
||||||
|
private accountsTableRows = (): ITableRow[] => {
|
||||||
|
return this.accountsToTableRowsMap(this.data.accounts);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the given total node to table row.
|
||||||
|
* @returns {ITableRow}
|
||||||
|
*/
|
||||||
|
private totalTableRow = (): ITableRow => {
|
||||||
|
return this.totalNodeTableRowsMapper(this.data.total);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the table rows.
|
||||||
|
* @returns {ITableRow[]}
|
||||||
|
*/
|
||||||
|
public tableRows = (): ITableRow[] => {
|
||||||
|
return R.compose(
|
||||||
|
R.unless(R.isEmpty, R.append(this.totalTableRow())),
|
||||||
|
R.concat(this.accountsTableRows()),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrrieves the table columns.
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
public tableColumns = (): ITableColumn[] => {
|
||||||
|
return R.compose(
|
||||||
|
this.tableColumnsCellIndexing,
|
||||||
|
R.concat([
|
||||||
|
{ key: 'account', label: 'Account' },
|
||||||
|
{ key: 'debit', label: 'Debit' },
|
||||||
|
{ key: 'credit', label: 'Credit' },
|
||||||
|
{ key: 'total', label: 'Total' },
|
||||||
|
]),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
ITrialBalanceSheetQuery,
|
||||||
|
ITrialBalanceSheetTable,
|
||||||
|
} from './TrialBalanceSheet.types';
|
||||||
|
import { TrialBalanceSheetTable } from './TrialBalanceSheetTable';
|
||||||
|
import { TrialBalanceSheetService } from './TrialBalanceSheetInjectable';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TrialBalanceSheetTableInjectable {
|
||||||
|
constructor(
|
||||||
|
private readonly sheet: TrialBalanceSheetService,
|
||||||
|
private readonly i18n: I18nService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the trial balance sheet table.
|
||||||
|
* @param {ITrialBalanceSheetQuery} query
|
||||||
|
* @returns {Promise<ITrialBalanceSheetTable>}
|
||||||
|
*/
|
||||||
|
public async table(
|
||||||
|
query: ITrialBalanceSheetQuery,
|
||||||
|
): Promise<ITrialBalanceSheetTable> {
|
||||||
|
const trialBalance = await this.sheet.trialBalanceSheet(query);
|
||||||
|
const table = new TrialBalanceSheetTable(
|
||||||
|
trialBalance.data,
|
||||||
|
query,
|
||||||
|
this.i18n,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
table: {
|
||||||
|
columns: table.tableColumns(),
|
||||||
|
rows: table.tableRows(),
|
||||||
|
},
|
||||||
|
meta: trialBalance.meta,
|
||||||
|
query: trialBalance.query,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
export enum IROW_TYPE {
|
||||||
|
ACCOUNT = 'ACCOUNT',
|
||||||
|
TOTAL = 'TOTAL',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HtmlTableCustomCss = `
|
||||||
|
table tr.row-type--total td{
|
||||||
|
border-top: 1px solid #bbb;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 3px double #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
table .column--account {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table .column--debit,
|
||||||
|
table .column--credit,
|
||||||
|
table .column--total,
|
||||||
|
table .cell--debit,
|
||||||
|
table .cell--credit,
|
||||||
|
table .cell--total{
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
export const getTrialBalanceSheetDefaultQuery = () => ({
|
||||||
|
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||||
|
toDate: moment().format('YYYY-MM-DD'),
|
||||||
|
numberFormat: {
|
||||||
|
divideOn1000: false,
|
||||||
|
negativeFormat: 'mines',
|
||||||
|
showZero: false,
|
||||||
|
formatMoney: 'total',
|
||||||
|
precision: 2,
|
||||||
|
},
|
||||||
|
basis: 'accrual',
|
||||||
|
noneZero: false,
|
||||||
|
noneTransactions: true,
|
||||||
|
onlyActive: false,
|
||||||
|
accountIds: [],
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
export interface IColumnMapperMeta {
|
export interface IColumnMapperMeta {
|
||||||
key: string;
|
key: string;
|
||||||
accessor?: string;
|
accessor?: string | ((value: any) => string);
|
||||||
value?: string;
|
value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ export interface ITableCell {
|
|||||||
|
|
||||||
export type ITableRow = {
|
export type ITableRow = {
|
||||||
cells: ITableCell[];
|
cells: ITableCell[];
|
||||||
rowTypes?: Array<any>
|
rowTypes?: Array<any>;
|
||||||
id?: string;
|
id?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ export interface IFinancialTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IFinancialTableTotal {
|
export interface IFinancialTableTotal {
|
||||||
amount: number;
|
amount: number;
|
||||||
formattedAmount: string;
|
formattedAmount: string;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user