mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-14 03:40:31 +00:00
refactor: financial statements to nestjs
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
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 { BaseModel } from '@/models/Model';
|
||||
import { Account } from './Account.model';
|
||||
@@ -10,6 +11,7 @@ export class AccountTransaction extends BaseModel {
|
||||
public readonly referenceId: number;
|
||||
public readonly accountId: number;
|
||||
public readonly contactId: number;
|
||||
public readonly contactType: string;
|
||||
public readonly credit: number;
|
||||
public readonly debit: number;
|
||||
public readonly exchangeRate: number;
|
||||
|
||||
@@ -3,6 +3,10 @@ import { PurchasesByItemsModule } from './modules/PurchasesByItems/PurchasesByIt
|
||||
import { CustomerBalanceSummaryModule } from './modules/CustomerBalanceSummary/CustomerBalanceSummary.module';
|
||||
import { SalesByItemsModule } from './modules/SalesByItems/SalesByItems.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({
|
||||
providers: [],
|
||||
@@ -10,7 +14,14 @@ import { GeneralLedgerModule } from './modules/GeneralLedger/GeneralLedger.modul
|
||||
PurchasesByItemsModule,
|
||||
CustomerBalanceSummaryModule,
|
||||
SalesByItemsModule,
|
||||
GeneralLedgerModule
|
||||
GeneralLedgerModule,
|
||||
TrialBalanceSheetModule,
|
||||
TransactionsByCustomerModule,
|
||||
TransactionsByVendorModule,
|
||||
// TransactionsByReferenceModule,
|
||||
// TransactionsByVendorModule,
|
||||
// TransactionsByContactModule,
|
||||
],
|
||||
})
|
||||
export class FinancialStatementsModule {}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ export class FinancialSheet {
|
||||
* @param {string} format
|
||||
* @returns
|
||||
*/
|
||||
protected getDateMeta(date: Date, format = 'YYYY-MM-DD') {
|
||||
protected getDateMeta(date: moment.MomentInput, format = 'YYYY-MM-DD') {
|
||||
return {
|
||||
formattedDate: moment(date).format(format),
|
||||
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 {
|
||||
key: string;
|
||||
accessor?: string;
|
||||
accessor?: string | ((value: any) => string);
|
||||
value?: string;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface ITableCell {
|
||||
|
||||
export type ITableRow = {
|
||||
cells: ITableCell[];
|
||||
rowTypes?: Array<any>
|
||||
rowTypes?: Array<any>;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
@@ -42,7 +42,7 @@ export interface IFinancialTable {
|
||||
}
|
||||
|
||||
export interface IFinancialTableTotal {
|
||||
amount: number;
|
||||
formattedAmount: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
amount: number;
|
||||
formattedAmount: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user