feat: wip clickhouse reports
This commit is contained in:
@@ -106,3 +106,11 @@ STRIPE_PAYMENT_CLIENT_ID=
|
||||
STRIPE_PAYMENT_WEBHOOKS_SECRET=
|
||||
# Replace example.com with the correct domain
|
||||
STRIPE_PAYMENT_REDIRECT_URL=https://example.com/preferences/payment-methods/stripe/callback
|
||||
|
||||
# ClickHouse
|
||||
CLICKHOUSE_ENABLED=false
|
||||
CLICKHOUSE_HOST=localhost
|
||||
CLICKHOUSE_PORT=8123
|
||||
CLICKHOUSE_USER=default
|
||||
CLICKHOUSE_PASSWORD=
|
||||
CLICKHOUSE_DATABASE=bigcapital_analytics
|
||||
|
||||
@@ -33,9 +33,11 @@ services:
|
||||
links:
|
||||
- mysql
|
||||
- redis
|
||||
- clickhouse
|
||||
depends_on:
|
||||
- mysql
|
||||
- redis
|
||||
- clickhouse
|
||||
restart: on-failure
|
||||
networks:
|
||||
- bigcapital_network
|
||||
@@ -127,6 +129,14 @@ services:
|
||||
- STRIPE_PAYMENT_WEBHOOKS_SECRET=${STRIPE_PAYMENT_WEBHOOKS_SECRET}
|
||||
- STRIPE_PAYMENT_REDIRECT_URL=${STRIPE_PAYMENT_REDIRECT_URL}
|
||||
|
||||
# ClickHouse
|
||||
- CLICKHOUSE_ENABLED=${CLICKHOUSE_ENABLED:-true}
|
||||
- CLICKHOUSE_HOST=${CLICKHOUSE_HOST:-clickhouse}
|
||||
- CLICKHOUSE_PORT=${CLICKHOUSE_PORT:-8123}
|
||||
- CLICKHOUSE_USER=${CLICKHOUSE_USER:-default}
|
||||
- CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
|
||||
- CLICKHOUSE_DATABASE=${CLICKHOUSE_DATABASE:-bigcapital_analytics}
|
||||
|
||||
database_migration:
|
||||
container_name: bigcapital-database-migration
|
||||
build:
|
||||
@@ -175,6 +185,18 @@ services:
|
||||
networks:
|
||||
- bigcapital_network
|
||||
|
||||
clickhouse:
|
||||
container_name: bigcapital-clickhouse
|
||||
image: clickhouse/clickhouse-server:24.8
|
||||
restart: on-failure
|
||||
expose:
|
||||
- '8123'
|
||||
- '9000'
|
||||
volumes:
|
||||
- clickhouse:/var/lib/clickhouse
|
||||
networks:
|
||||
- bigcapital_network
|
||||
|
||||
gotenberg:
|
||||
image: gotenberg/gotenberg:7
|
||||
expose:
|
||||
@@ -192,6 +214,10 @@ volumes:
|
||||
name: bigcapital_prod_redis
|
||||
driver: local
|
||||
|
||||
clickhouse:
|
||||
name: bigcapital_prod_clickhouse
|
||||
driver: local
|
||||
|
||||
# Networks
|
||||
networks:
|
||||
bigcapital_network:
|
||||
|
||||
@@ -42,6 +42,20 @@ services:
|
||||
ports:
|
||||
- '9000:3000'
|
||||
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:24.8
|
||||
container_name: bigcapital-clickhouse
|
||||
ports:
|
||||
- '8123:8123'
|
||||
- '9001:9000'
|
||||
volumes:
|
||||
- clickhouse:/var/lib/clickhouse
|
||||
environment:
|
||||
CLICKHOUSE_DB: bigcapital_analytics
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: unless-stopped
|
||||
|
||||
# Volumes
|
||||
volumes:
|
||||
mysql:
|
||||
@@ -51,3 +65,7 @@ volumes:
|
||||
redis:
|
||||
name: bigcapital_dev_redis
|
||||
driver: local
|
||||
|
||||
clickhouse:
|
||||
name: bigcapital_dev_clickhouse
|
||||
driver: local
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mariadb:10.2
|
||||
FROM mariadb:11.4
|
||||
|
||||
USER root
|
||||
ADD my.cnf /etc/mysql/conf.d/my.cnf
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
GRANT ALL PRIVILEGES ON *.* TO '{MYSQL_USER}'@'%' IDENTIFIED BY '{MYSQL_PASSWORD}' WITH GRANT OPTION;
|
||||
|
||||
FLUSH PRIVILEGES;
|
||||
-- Create ClickHouse CDC replication user
|
||||
CREATE USER IF NOT EXISTS 'clickpipes_user'@'%' IDENTIFIED BY 'clickpipes_password';
|
||||
GRANT SELECT ON *.* TO 'clickpipes_user'@'%';
|
||||
GRANT REPLICATION CLIENT ON *.* TO 'clickpipes_user'@'%';
|
||||
GRANT REPLICATION SLAVE ON *.* TO 'clickpipes_user'@'%';
|
||||
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
+16
-1
@@ -1,2 +1,17 @@
|
||||
[mysqld]
|
||||
bind-address = 0.0.0.0
|
||||
bind-address = 0.0.0.0
|
||||
|
||||
# Binary logging for ClickHouse CDC (MaterializedMySQL)
|
||||
server_id = 1
|
||||
log_bin = ON
|
||||
binlog_format = ROW
|
||||
binlog_row_image = FULL
|
||||
binlog_row_metadata = FULL
|
||||
binlog_expire_logs_seconds = 86400
|
||||
|
||||
# Required for replication from replica
|
||||
log_slave_updates = ON
|
||||
|
||||
# Character set
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
|
||||
@@ -95,4 +95,12 @@ STRIPE_PAYMENT_SECRET_KEY=
|
||||
STRIPE_PAYMENT_PUBLISHABLE_KEY=
|
||||
STRIPE_PAYMENT_CLIENT_ID=
|
||||
STRIPE_PAYMENT_WEBHOOKS_SECRET=
|
||||
STRIPE_PAYMENT_REDIRECT_URL=
|
||||
STRIPE_PAYMENT_REDIRECT_URL=
|
||||
|
||||
# ClickHouse
|
||||
CLICKHOUSE_ENABLED=false
|
||||
CLICKHOUSE_HOST=localhost
|
||||
CLICKHOUSE_PORT=8123
|
||||
CLICKHOUSE_USER=default
|
||||
CLICKHOUSE_PASSWORD=
|
||||
CLICKHOUSE_DATABASE=bigcapital_analytics
|
||||
Generated
+17449
File diff suppressed because it is too large
Load Diff
@@ -38,13 +38,14 @@
|
||||
"@bigcapital/pdf-templates": "workspace:*",
|
||||
"@bigcapital/sdk-ts": "workspace:*",
|
||||
"@bigcapital/utils": "workspace:*",
|
||||
"@casl/ability": "^5.4.3",
|
||||
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
||||
"@liaoliaots/nestjs-redis": "^10.0.0",
|
||||
"@nest-lab/throttler-storage-redis": "^1.1.0",
|
||||
"@bull-board/api": "^5.22.0",
|
||||
"@bull-board/express": "^5.22.0",
|
||||
"@bull-board/nestjs": "^5.22.0",
|
||||
"@casl/ability": "^5.4.3",
|
||||
"@clickhouse/client": "^1.18.3",
|
||||
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
||||
"@liaoliaots/nestjs-redis": "^10.0.0",
|
||||
"@nest-lab/throttler-storage-redis": "^1.1.0",
|
||||
"@nestjs/bull": "^10.2.1",
|
||||
"@nestjs/bullmq": "^10.2.2",
|
||||
"@nestjs/cache-manager": "^2.2.2",
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('clickhouse', () => ({
|
||||
enabled: process.env.CLICKHOUSE_ENABLED === 'true',
|
||||
host: process.env.CLICKHOUSE_HOST || 'localhost',
|
||||
port: parseInt(process.env.CLICKHOUSE_PORT || '8123', 10),
|
||||
user: process.env.CLICKHOUSE_USER || 'default',
|
||||
password: process.env.CLICKHOUSE_PASSWORD || '',
|
||||
database: process.env.CLICKHOUSE_DATABASE || 'bigcapital_analytics',
|
||||
}));
|
||||
@@ -20,6 +20,7 @@ import cloud from './cloud';
|
||||
import redis from './redis';
|
||||
import queue from './queue';
|
||||
import bullBoard from './bull-board';
|
||||
import clickhouse from './clickhouse-database';
|
||||
|
||||
export const config = [
|
||||
app,
|
||||
@@ -44,4 +45,5 @@ export const config = [
|
||||
redis,
|
||||
queue,
|
||||
bullBoard,
|
||||
clickhouse,
|
||||
];
|
||||
|
||||
@@ -85,6 +85,7 @@ import { TenantDBManagerModule } from '../TenantDBManager/TenantDBManager.module
|
||||
import { PaymentServicesModule } from '../PaymentServices/PaymentServices.module';
|
||||
import { AuthModule } from '../Auth/Auth.module';
|
||||
import { TenancyModule } from '../Tenancy/Tenancy.module';
|
||||
import { ClickHouseModule } from '../ClickHouse/ClickHouse.module';
|
||||
import { LoopsModule } from '../Loops/Loops.module';
|
||||
import { AttachmentsModule } from '../Attachments/Attachment.module';
|
||||
import { S3Module } from '../S3/S3.module';
|
||||
@@ -119,6 +120,7 @@ import { AppThrottleModule } from './AppThrottle.module';
|
||||
}),
|
||||
SystemDatabaseModule,
|
||||
SystemModelsModule,
|
||||
ClickHouseModule,
|
||||
EventEmitterModule.forRoot(),
|
||||
I18nModule.forRootAsync({
|
||||
useFactory: () => ({
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export const CLICKHOUSE_CLIENT = Symbol('CLICKHOUSE_CLIENT');
|
||||
export const CLICKHOUSE_CONNECTION_OPTIONS = Symbol('CLICKHOUSE_CONNECTION_OPTIONS');
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { ClickHouseService } from './ClickHouse.service';
|
||||
import { ClickHouseMigrationService } from './ClickHouseMigration.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [ClickHouseService, ClickHouseMigrationService],
|
||||
exports: [ClickHouseService, ClickHouseMigrationService],
|
||||
})
|
||||
export class ClickHouseModule {}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { createClient, ClickHouseClient } from '@clickhouse/client';
|
||||
import * as LRUCache from 'lru-cache';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
|
||||
@Injectable({ scope: Scope.DEFAULT })
|
||||
export class ClickHouseService {
|
||||
private readonly cache: LRUCache<string, ClickHouseClient>;
|
||||
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly cls: ClsService,
|
||||
) {
|
||||
this.cache = new LRUCache({ max: 100 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves or creates a ClickHouse client.
|
||||
* If database is not specified, uses the tenant-specific database.
|
||||
*/
|
||||
getClient(database?: string): ClickHouseClient {
|
||||
const organizationId = this.cls.get('organizationId');
|
||||
const tenantId = organizationId ? String(organizationId) : 'system';
|
||||
const dbName = database || this.getTenantDatabaseName(tenantId);
|
||||
const cacheKey = `${tenantId}:${dbName}`;
|
||||
const cachedClient = this.cache.get(cacheKey);
|
||||
|
||||
if (cachedClient) {
|
||||
return cachedClient;
|
||||
}
|
||||
|
||||
const host = this.configService.get<string>('clickhouse.host', 'localhost');
|
||||
const port = this.configService.get<number>('clickhouse.port', 8123);
|
||||
const user = this.configService.get<string>('clickhouse.user', 'default');
|
||||
const password = this.configService.get<string>('clickhouse.password', '');
|
||||
|
||||
const client = createClient({
|
||||
host: `http://${host}:${port}`,
|
||||
username: user,
|
||||
password,
|
||||
database: dbName,
|
||||
request_timeout: 30000,
|
||||
max_open_connections: 10,
|
||||
});
|
||||
|
||||
this.cache.set(cacheKey, client);
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default ClickHouse database name for a tenant.
|
||||
*/
|
||||
getTenantDatabaseName(tenantId: string): string {
|
||||
const prefix = this.configService.get<string>(
|
||||
'tenantDatabase.dbNamePrefix',
|
||||
'bigcapital_tenant_',
|
||||
);
|
||||
return `${prefix}${tenantId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a query and returns the result.
|
||||
* Uses the tenant database by default.
|
||||
*/
|
||||
async query<T = any>(
|
||||
query: string,
|
||||
params?: Record<string, unknown>,
|
||||
database?: string,
|
||||
): Promise<T[]> {
|
||||
const client = this.getClient(database);
|
||||
const resultSet = await client.query({
|
||||
query,
|
||||
query_params: params,
|
||||
format: 'JSONEachRow',
|
||||
});
|
||||
return await resultSet.json<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a command (DDL, INSERT, etc.).
|
||||
*/
|
||||
async command(query: string, database?: string): Promise<void> {
|
||||
const client = this.getClient(database);
|
||||
await client.command({ query });
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts data into a table.
|
||||
*/
|
||||
async insert<T = any>(table: string, values: T[], database?: string): Promise<void> {
|
||||
const client = this.getClient(database);
|
||||
await client.insert({
|
||||
table,
|
||||
values,
|
||||
format: 'JSONEachRow',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { ClickHouseService } from './ClickHouse.service';
|
||||
|
||||
@Injectable()
|
||||
export class ClickHouseMigrationService {
|
||||
constructor(
|
||||
private readonly clickHouse: ClickHouseService,
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Bootstraps MaterializedMySQL replication for a tenant database.
|
||||
*/
|
||||
async bootstrapTenantReplication(tenantDbName: string): Promise<void> {
|
||||
const enabled = this.configService.get<boolean>('clickhouse.enabled', false);
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
const mariadbHost = this.configService.get<string>('tenantDatabase.host', 'mariadb');
|
||||
const mariadbPort = this.configService.get<number>('tenantDatabase.port', 3306);
|
||||
const mariadbUser = this.configService.get<string>('tenantDatabase.user', 'clickpipes_user');
|
||||
const mariadbPassword = this.configService.get<string>('tenantDatabase.password', 'clickpipes_password');
|
||||
|
||||
// MaterializedMySQL requires the database to not exist before creation
|
||||
const checkQuery = `SELECT count() FROM system.databases WHERE name = {tenantDb:String}`;
|
||||
const exists = await this.clickHouse.query<{ count: number }>(checkQuery, {
|
||||
tenantDb: tenantDbName,
|
||||
});
|
||||
if (exists[0]?.count > 0) {
|
||||
return;
|
||||
}
|
||||
const createQuery = `
|
||||
CREATE DATABASE IF NOT EXISTS ${tenantDbName}
|
||||
ENGINE = MaterializedMySQL('${mariadbHost}:${mariadbPort}', '${tenantDbName}', '${mariadbUser}', '${mariadbPassword}')
|
||||
SETTINGS
|
||||
materialized_mysql_tables_list = 'accounts_transactions',
|
||||
materialized_mysql_wait_for replication,
|
||||
materialized_mysql_snapshot_mode = 'standard'
|
||||
`;
|
||||
|
||||
await this.clickHouse.command(createQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates pre-aggregated balance tables for a tenant.
|
||||
* These tables are populated from MaterializedMySQL replicated data.
|
||||
*/
|
||||
async createPreAggregatedTables(tenantDbName: string): Promise<void> {
|
||||
const enabled = this.configService.get<boolean>('clickhouse.enabled', false);
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// SummingMergeTree for daily account balances
|
||||
const createBalancesTable = `
|
||||
CREATE TABLE IF NOT EXISTS ${tenantDbName}.account_balances_daily (
|
||||
account_id UInt32,
|
||||
date Date,
|
||||
credit_sum Decimal(15, 5),
|
||||
debit_sum Decimal(15, 5),
|
||||
branch_id Nullable(UInt32)
|
||||
) ENGINE = SummingMergeTree()
|
||||
ORDER BY (account_id, date, branch_id)
|
||||
`;
|
||||
|
||||
await this.clickHouse.command(createBalancesTable);
|
||||
|
||||
// Materialized view to auto-populate from replicated transactions
|
||||
const createMv = `
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS ${tenantDbName}.mv_account_balances_daily
|
||||
TO ${tenantDbName}.account_balances_daily
|
||||
AS SELECT
|
||||
account_id,
|
||||
date,
|
||||
sum(credit) AS credit_sum,
|
||||
sum(debit) AS debit_sum,
|
||||
branch_id
|
||||
FROM ${tenantDbName}.accounts_transactions
|
||||
GROUP BY account_id, date, branch_id
|
||||
`;
|
||||
await this.clickHouse.command(createMv);
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -9,13 +9,18 @@ import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module';
|
||||
import { BalanceSheetStatementController } from './BalanceSheet.controller';
|
||||
import { BalanceSheetRepository } from './BalanceSheetRepository';
|
||||
import { BalanceSheetClickHouseRepository } from './BalanceSheetClickHouseRepository';
|
||||
import { BalanceSheetRepositoryFactory } from './BalanceSheetRepositoryFactory';
|
||||
import { AccountsModule } from '@/modules/Accounts/Accounts.module';
|
||||
import { ClickHouseModule } from '@/modules/ClickHouse/ClickHouse.module';
|
||||
|
||||
@Module({
|
||||
imports: [FinancialSheetCommonModule, AccountsModule],
|
||||
imports: [FinancialSheetCommonModule, AccountsModule, ClickHouseModule],
|
||||
controllers: [BalanceSheetStatementController],
|
||||
providers: [
|
||||
BalanceSheetRepository,
|
||||
BalanceSheetClickHouseRepository,
|
||||
BalanceSheetRepositoryFactory,
|
||||
BalanceSheetInjectable,
|
||||
BalanceSheetTableInjectable,
|
||||
BalanceSheetExportInjectable,
|
||||
|
||||
+438
@@ -0,0 +1,438 @@
|
||||
// @ts-nocheck
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import * as R from 'ramda';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { ModelObject } from 'objection';
|
||||
import { IBalanceSheetQuery, IAccountTransactionsGroupBy } from './BalanceSheet.types';
|
||||
import { BalanceSheetQuery } from './BalanceSheetQuery';
|
||||
import { ILedger } from '@/modules/Ledger/types/Ledger.types';
|
||||
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||
import { transformToMapBy } from '@/utils/transform-to-map-by';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { ClickHouseService } from '@/modules/ClickHouse/ClickHouse.service';
|
||||
import { ACCOUNT_PARENT_TYPE } from '@/constants/accounts';
|
||||
import { IBalanceSheetRepository } from './IBalanceSheetRepository';
|
||||
|
||||
/**
|
||||
* Maps ClickHouse query result row to a ledger-compatible transaction object.
|
||||
*/
|
||||
interface CHTransactionRow {
|
||||
account_id: number;
|
||||
credit: string | number;
|
||||
debit: string | number;
|
||||
date?: string;
|
||||
account_normal?: string;
|
||||
account_type?: string;
|
||||
account_parent_type?: string;
|
||||
}
|
||||
|
||||
@Injectable({ scope: Scope.TRANSIENT })
|
||||
export class BalanceSheetClickHouseRepository implements IBalanceSheetRepository {
|
||||
@Inject(Account.name)
|
||||
public readonly accountModel: TenantModelProxy<typeof Account>;
|
||||
|
||||
@Inject(ClickHouseService)
|
||||
private readonly clickHouse: ClickHouseService;
|
||||
|
||||
public query: BalanceSheetQuery;
|
||||
public accounts: ModelObject<Account>[] = [];
|
||||
public accountsGraph: any;
|
||||
public accountsByType: Map<string, ModelObject<Account>[]> = new Map();
|
||||
public accountsByParentType: Map<string, ModelObject<Account>[]> = new Map();
|
||||
|
||||
public totalAccountsLedger: ILedger;
|
||||
public incomeLedger: ILedger;
|
||||
public expensesLedger: ILedger;
|
||||
|
||||
public periodsAccountsLedger: ILedger;
|
||||
public periodsOpeningAccountLedger: ILedger;
|
||||
|
||||
public PYTotalAccountsLedger: ILedger;
|
||||
public PYPeriodsAccountsLedger: ILedger;
|
||||
public PYPeriodsOpeningAccountLedger: ILedger;
|
||||
|
||||
public PPTotalAccountsLedger: ILedger;
|
||||
public PPPeriodsAccountsLedger: ILedger;
|
||||
public PPPeriodsOpeningAccountLedger: ILedger;
|
||||
|
||||
public incomePeriodsAccountsLedger: ILedger;
|
||||
public incomePeriodsOpeningAccountsLedger: ILedger;
|
||||
public expensesPeriodsAccountsLedger: ILedger;
|
||||
public expensesOpeningAccountLedger: ILedger;
|
||||
|
||||
public incomePPAccountsLedger: ILedger;
|
||||
public expensePPAccountsLedger: ILedger;
|
||||
public incomePPPeriodsAccountsLedger: ILedger;
|
||||
public incomePPPeriodsOpeningAccountLedger: ILedger;
|
||||
public expensePPPeriodsAccountsLedger: ILedger;
|
||||
public expensePPPeriodsOpeningAccountLedger: ILedger;
|
||||
|
||||
public incomePYTotalAccountsLedger: ILedger;
|
||||
public expensePYTotalAccountsLedger: ILedger;
|
||||
public incomePYPeriodsAccountsLedger: ILedger;
|
||||
public incomePYPeriodsOpeningAccountLedger: ILedger;
|
||||
public expensePYPeriodsAccountsLedger: ILedger;
|
||||
public expensePYPeriodsOpeningAccountLedger: ILedger;
|
||||
|
||||
public transactionsGroupType: IAccountTransactionsGroupBy = IAccountTransactionsGroupBy.Month;
|
||||
|
||||
// Internal account normal map built from MySQL accounts
|
||||
private accountNormalMap: Map<number, string> = new Map();
|
||||
|
||||
public setQuery(query: IBalanceSheetQuery) {
|
||||
this.query = new BalanceSheetQuery(query);
|
||||
this.transactionsGroupType = this.getGroupByFromDisplayColumnsBy(
|
||||
this.query.displayColumnsBy,
|
||||
);
|
||||
}
|
||||
|
||||
public asyncInitialize = async (query: IBalanceSheetQuery) => {
|
||||
this.setQuery(query);
|
||||
|
||||
await this.initAccounts();
|
||||
await this.initAccountsGraph();
|
||||
await this.initAccountsTotalLedger();
|
||||
|
||||
if (this.query.isDatePeriodsColumnsType()) {
|
||||
await this.initTotalDatePeriods();
|
||||
}
|
||||
if (this.query.isPreviousYearActive()) {
|
||||
await this.initTotalPreviousYear();
|
||||
}
|
||||
if (
|
||||
this.query.isPreviousYearActive() &&
|
||||
this.query.isDatePeriodsColumnsType()
|
||||
) {
|
||||
await this.initPeriodsPreviousYear();
|
||||
}
|
||||
if (this.query.isPreviousPeriodActive()) {
|
||||
await this.initTotalPreviousPeriod();
|
||||
}
|
||||
if (
|
||||
this.query.isPreviousPeriodActive() &&
|
||||
this.query.isDatePeriodsColumnsType()
|
||||
) {
|
||||
await this.initPeriodsPreviousPeriod();
|
||||
}
|
||||
|
||||
await this.asyncInitializeNetIncome();
|
||||
};
|
||||
|
||||
// ----------------------------
|
||||
// # Accounts
|
||||
// ----------------------------
|
||||
public initAccounts = async () => {
|
||||
const accounts = await this.getAccounts();
|
||||
this.accounts = accounts;
|
||||
this.accountsByType = transformToMapBy(accounts, 'accountType');
|
||||
this.accountsByParentType = transformToMapBy(accounts, 'accountParentType');
|
||||
|
||||
// Build account normal map for ledger entries
|
||||
accounts.forEach((acc) => {
|
||||
this.accountNormalMap.set(acc.id, acc.accountNormal);
|
||||
});
|
||||
};
|
||||
|
||||
public initAccountsGraph = async () => {
|
||||
this.accountsGraph = this.accountModel().toDependencyGraph(this.accounts);
|
||||
};
|
||||
|
||||
public getAccounts = () => {
|
||||
return this.accountModel().query();
|
||||
};
|
||||
|
||||
// ----------------------------
|
||||
// # Closing Total
|
||||
// ----------------------------
|
||||
public initAccountsTotalLedger = async (): Promise<void> => {
|
||||
const totalByAccount = await this.closingAccountsTotal(this.query.toDate);
|
||||
this.totalAccountsLedger = Ledger.fromTransactions(totalByAccount);
|
||||
};
|
||||
|
||||
// ----------------------------
|
||||
// # Date periods.
|
||||
// ----------------------------
|
||||
public initTotalDatePeriods = async (): Promise<void> => {
|
||||
const [periodsByAccount, periodsOpeningByAccount] = await Promise.all([
|
||||
this.accountsDatePeriods(
|
||||
this.query.fromDate,
|
||||
this.query.toDate,
|
||||
this.transactionsGroupType,
|
||||
),
|
||||
this.closingAccountsTotal(this.query.fromDate),
|
||||
]);
|
||||
this.periodsAccountsLedger = Ledger.fromTransactions(periodsByAccount);
|
||||
this.periodsOpeningAccountLedger = Ledger.fromTransactions(periodsOpeningByAccount);
|
||||
};
|
||||
|
||||
// ----------------------------
|
||||
// # Previous Year (PY).
|
||||
// ----------------------------
|
||||
public initTotalPreviousYear = async (): Promise<void> => {
|
||||
const PYTotalsByAccounts = await this.closingAccountsTotal(this.query.PYToDate);
|
||||
this.PYTotalAccountsLedger = Ledger.fromTransactions(PYTotalsByAccounts);
|
||||
};
|
||||
|
||||
public initPeriodsPreviousYear = async (): Promise<void> => {
|
||||
const [PYPeriodsBYAccounts, periodsOpeningByAccount] = await Promise.all([
|
||||
this.accountsDatePeriods(
|
||||
this.query.PYFromDate,
|
||||
this.query.PYToDate,
|
||||
this.transactionsGroupType,
|
||||
),
|
||||
this.closingAccountsTotal(this.query.PYFromDate),
|
||||
]);
|
||||
this.PYPeriodsAccountsLedger = Ledger.fromTransactions(PYPeriodsBYAccounts);
|
||||
this.PYPeriodsOpeningAccountLedger = Ledger.fromTransactions(periodsOpeningByAccount);
|
||||
};
|
||||
|
||||
// ----------------------------
|
||||
// # Previous Year (PP).
|
||||
// ----------------------------
|
||||
public initTotalPreviousPeriod = async (): Promise<void> => {
|
||||
const PPTotalsByAccounts = await this.closingAccountsTotal(this.query.PPToDate);
|
||||
this.PPTotalAccountsLedger = Ledger.fromTransactions(PPTotalsByAccounts);
|
||||
};
|
||||
|
||||
public initPeriodsPreviousPeriod = async (): Promise<void> => {
|
||||
const [PPPeriodsBYAccounts, periodsOpeningByAccount] = await Promise.all([
|
||||
this.accountsDatePeriods(
|
||||
this.query.PPFromDate,
|
||||
this.query.PPToDate,
|
||||
this.transactionsGroupType,
|
||||
),
|
||||
this.closingAccountsTotal(this.query.PPFromDate),
|
||||
]);
|
||||
this.PPPeriodsAccountsLedger = Ledger.fromTransactions(PPPeriodsBYAccounts);
|
||||
this.PPPeriodsOpeningAccountLedger = Ledger.fromTransactions(periodsOpeningByAccount);
|
||||
};
|
||||
|
||||
// ----------------------------
|
||||
// # ClickHouse Queries
|
||||
// ----------------------------
|
||||
|
||||
/**
|
||||
* Retrieve closing accounts total up to a date from ClickHouse.
|
||||
*/
|
||||
public closingAccountsTotal = async (toDate: Date | string) => {
|
||||
const dateStr = this.formatDate(toDate);
|
||||
const branchFilter = this.buildBranchFilter();
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
account_id,
|
||||
sum(credit) AS credit,
|
||||
sum(debit) AS debit
|
||||
FROM accounts_transactions
|
||||
WHERE date <= {toDate:Date}
|
||||
${branchFilter}
|
||||
GROUP BY account_id
|
||||
`;
|
||||
|
||||
const rows = await this.clickHouse.query<CHTransactionRow>(query, { toDate: dateStr });
|
||||
return this.mapToLedgerTransactions(rows);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve account transactions grouped by date periods from ClickHouse.
|
||||
*/
|
||||
public accountsDatePeriods = async (
|
||||
fromDate: Date,
|
||||
toDate: Date,
|
||||
datePeriodsType: string,
|
||||
) => {
|
||||
const fromStr = this.formatDate(fromDate);
|
||||
const toStr = this.formatDate(toDate);
|
||||
const groupFormat = this.getClickHouseDateFormat(datePeriodsType);
|
||||
const branchFilter = this.buildBranchFilter();
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
account_id,
|
||||
formatDateTime(date, '${groupFormat}') AS date,
|
||||
sum(credit) AS credit,
|
||||
sum(debit) AS debit
|
||||
FROM accounts_transactions
|
||||
WHERE date >= {fromDate:Date}
|
||||
AND date <= {toDate:Date}
|
||||
${branchFilter}
|
||||
GROUP BY account_id, formatDateTime(date, '${groupFormat}')
|
||||
ORDER BY account_id, date
|
||||
`;
|
||||
|
||||
const rows = await this.clickHouse.query<CHTransactionRow>(query, {
|
||||
fromDate: fromStr,
|
||||
toDate: toStr,
|
||||
});
|
||||
return this.mapToLedgerTransactions(rows);
|
||||
};
|
||||
|
||||
// ----------------------------
|
||||
// # Net Income (mirrored from BalanceSheetRepositoryNetIncome)
|
||||
// ----------------------------
|
||||
public asyncInitializeNetIncome = async () => {
|
||||
this.initIncomeAccounts();
|
||||
this.initExpenseAccounts();
|
||||
this.initIncomeTotalLedger();
|
||||
this.initExpensesTotalLedger();
|
||||
|
||||
if (this.query.isDatePeriodsColumnsType()) {
|
||||
this.initNetIncomeDatePeriods();
|
||||
}
|
||||
if (this.query.isPreviousYearActive()) {
|
||||
this.initNetIncomePreviousYear();
|
||||
}
|
||||
if (this.query.isPreviousPeriodActive()) {
|
||||
this.initNetIncomePreviousPeriod();
|
||||
}
|
||||
if (
|
||||
this.query.isPreviousYearActive() &&
|
||||
this.query.isDatePeriodsColumnsType()
|
||||
) {
|
||||
this.initNetIncomePeriodsPreviewYear();
|
||||
}
|
||||
if (
|
||||
this.query.isPreviousPeriodActive() &&
|
||||
this.query.isDatePeriodsColumnsType()
|
||||
) {
|
||||
this.initNetIncomePeriodsPreviousPeriod();
|
||||
}
|
||||
};
|
||||
|
||||
public incomeAccounts: ModelObject<Account>[] = [];
|
||||
public incomeAccountsIds: number[] = [];
|
||||
public expenseAccounts: ModelObject<Account>[] = [];
|
||||
public expenseAccountsIds: number[] = [];
|
||||
|
||||
public initIncomeAccounts = () => {
|
||||
const incomeAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.INCOME) || [];
|
||||
const incomeAccountsIds = incomeAccounts.map((a) => a.id);
|
||||
this.incomeAccounts = incomeAccounts;
|
||||
this.incomeAccountsIds = incomeAccountsIds;
|
||||
};
|
||||
|
||||
public initExpenseAccounts = () => {
|
||||
const expenseAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.EXPENSE) || [];
|
||||
const expenseAccountsIds = expenseAccounts.map((a) => a.id);
|
||||
this.expenseAccounts = expenseAccounts;
|
||||
this.expenseAccountsIds = expenseAccountsIds;
|
||||
};
|
||||
|
||||
public initIncomeTotalLedger = (): void => {
|
||||
const incomeAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.INCOME) || [];
|
||||
const incomeAccountsIds = incomeAccounts.map((a) => a.id);
|
||||
this.incomeLedger = this.totalAccountsLedger.whereAccountsIds(incomeAccountsIds);
|
||||
};
|
||||
|
||||
public initExpensesTotalLedger = (): void => {
|
||||
const expenseAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.EXPENSE) || [];
|
||||
const expenseAccountsIds = expenseAccounts.map((a) => a.id);
|
||||
this.expensesLedger = this.totalAccountsLedger.whereAccountsIds(expenseAccountsIds);
|
||||
};
|
||||
|
||||
public initNetIncomeDatePeriods = () => {
|
||||
const incomeAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.INCOME) || [];
|
||||
const incomeAccountsIds = incomeAccounts.map((a) => a.id);
|
||||
const expenseAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.EXPENSE) || [];
|
||||
const expenseAccountsIds = expenseAccounts.map((a) => a.id);
|
||||
|
||||
this.incomePeriodsAccountsLedger = this.periodsAccountsLedger.whereAccountsIds(incomeAccountsIds);
|
||||
this.incomePeriodsOpeningAccountsLedger = this.periodsOpeningAccountLedger.whereAccountsIds(incomeAccountsIds);
|
||||
this.expensesPeriodsAccountsLedger = this.periodsAccountsLedger.whereAccountsIds(expenseAccountsIds);
|
||||
this.expensesOpeningAccountLedger = this.periodsOpeningAccountLedger.whereAccountsIds(expenseAccountsIds);
|
||||
};
|
||||
|
||||
public initNetIncomePreviousPeriod = () => {
|
||||
const incomeAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.INCOME) || [];
|
||||
const incomeAccountsIds = incomeAccounts.map((a) => a.id);
|
||||
const expenseAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.EXPENSE) || [];
|
||||
const expenseAccountsIds = expenseAccounts.map((a) => a.id);
|
||||
|
||||
this.incomePPAccountsLedger = this.PPTotalAccountsLedger.whereAccountsIds(incomeAccountsIds);
|
||||
this.expensePPAccountsLedger = this.PPTotalAccountsLedger.whereAccountsIds(expenseAccountsIds);
|
||||
};
|
||||
|
||||
public initNetIncomePeriodsPreviousPeriod = () => {
|
||||
const incomeAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.INCOME) || [];
|
||||
const incomeAccountsIds = incomeAccounts.map((a) => a.id);
|
||||
const expenseAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.EXPENSE) || [];
|
||||
const expenseAccountsIds = expenseAccounts.map((a) => a.id);
|
||||
|
||||
this.incomePPPeriodsAccountsLedger = this.PPPeriodsAccountsLedger.whereAccountsIds(incomeAccountsIds);
|
||||
this.incomePPPeriodsOpeningAccountLedger = this.PPPeriodsOpeningAccountLedger.whereAccountsIds(incomeAccountsIds);
|
||||
this.expensePPPeriodsAccountsLedger = this.PPPeriodsAccountsLedger.whereAccountsIds(expenseAccountsIds);
|
||||
this.expensePPPeriodsOpeningAccountLedger = this.PPPeriodsOpeningAccountLedger.whereAccountsIds(expenseAccountsIds);
|
||||
};
|
||||
|
||||
public initNetIncomePreviousYear = () => {
|
||||
const incomeAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.INCOME) || [];
|
||||
const incomeAccountsIds = incomeAccounts.map((a) => a.id);
|
||||
const expenseAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.EXPENSE) || [];
|
||||
const expenseAccountsIds = expenseAccounts.map((a) => a.id);
|
||||
|
||||
this.incomePYTotalAccountsLedger = this.PYTotalAccountsLedger.whereAccountsIds(incomeAccountsIds);
|
||||
this.expensePYTotalAccountsLedger = this.PYTotalAccountsLedger.whereAccountsIds(expenseAccountsIds);
|
||||
};
|
||||
|
||||
public initNetIncomePeriodsPreviewYear = () => {
|
||||
const incomeAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.INCOME) || [];
|
||||
const incomeAccountsIds = incomeAccounts.map((a) => a.id);
|
||||
const expenseAccounts = this.accountsByParentType.get(ACCOUNT_PARENT_TYPE.EXPENSE) || [];
|
||||
const expenseAccountsIds = expenseAccounts.map((a) => a.id);
|
||||
|
||||
this.incomePYPeriodsAccountsLedger = this.PYPeriodsAccountsLedger.whereAccountsIds(incomeAccountsIds);
|
||||
this.incomePYPeriodsOpeningAccountLedger = this.PYPeriodsOpeningAccountLedger.whereAccountsIds(incomeAccountsIds);
|
||||
this.expensePYPeriodsAccountsLedger = this.PYPeriodsAccountsLedger.whereAccountsIds(expenseAccountsIds);
|
||||
this.expensePYPeriodsOpeningAccountLedger = this.PYPeriodsOpeningAccountLedger.whereAccountsIds(expenseAccountsIds);
|
||||
};
|
||||
|
||||
// ----------------------------
|
||||
// # Helpers
|
||||
// ----------------------------
|
||||
|
||||
private mapToLedgerTransactions(rows: CHTransactionRow[]): any[] {
|
||||
return rows.map((row) => ({
|
||||
accountId: row.account_id,
|
||||
credit: Number(row.credit) || 0,
|
||||
debit: Number(row.debit) || 0,
|
||||
date: row.date,
|
||||
account: {
|
||||
accountNormal: this.accountNormalMap.get(row.account_id) || 'debit',
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
private formatDate(date: Date | string): string {
|
||||
const d = new Date(date);
|
||||
return d.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
private getClickHouseDateFormat(groupType: string): string {
|
||||
const formats: Record<string, string> = {
|
||||
day: '%Y-%m-%d',
|
||||
month: '%Y-%m',
|
||||
year: '%Y',
|
||||
};
|
||||
return formats[groupType] || '%Y-%m';
|
||||
}
|
||||
|
||||
private buildBranchFilter(): string {
|
||||
if (!isEmpty(this.query?.branchesIds)) {
|
||||
const ids = this.query.branchesIds.map((id: number) => String(id)).join(',');
|
||||
return `AND branch_id IN (${ids})`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private getGroupByFromDisplayColumnsBy(columnsBy: string): IAccountTransactionsGroupBy {
|
||||
const mapping: Record<string, IAccountTransactionsGroupBy> = {
|
||||
week: IAccountTransactionsGroupBy.Day,
|
||||
quarter: IAccountTransactionsGroupBy.Month,
|
||||
year: IAccountTransactionsGroupBy.Year,
|
||||
month: IAccountTransactionsGroupBy.Month,
|
||||
day: IAccountTransactionsGroupBy.Day,
|
||||
};
|
||||
return mapping[columnsBy] || IAccountTransactionsGroupBy.Month;
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -3,7 +3,7 @@ import {
|
||||
IBalanceSheetDOO,
|
||||
IBalanceSheetQuery,
|
||||
} from './BalanceSheet.types';
|
||||
import { BalanceSheetRepository } from './BalanceSheetRepository';
|
||||
import { BalanceSheetRepositoryFactory } from './BalanceSheetRepositoryFactory';
|
||||
import { BalanceSheetMetaInjectable } from './BalanceSheetMeta';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
@@ -20,7 +20,8 @@ export class BalanceSheetInjectable {
|
||||
private readonly eventPublisher: EventEmitter2,
|
||||
private readonly tenancyContext: TenancyContext,
|
||||
private readonly i18n: I18nService,
|
||||
private readonly balanceSheetRepository: BalanceSheetRepository,
|
||||
@Inject(BalanceSheetRepositoryFactory)
|
||||
private readonly repositoryFactory: BalanceSheetRepositoryFactory,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -38,7 +39,8 @@ export class BalanceSheetInjectable {
|
||||
const tenantMetadata = await this.tenancyContext.getTenantMetadata(true);
|
||||
|
||||
// Loads all resources.
|
||||
await this.balanceSheetRepository.asyncInitialize(filter);
|
||||
const repository = this.repositoryFactory.getRepository();
|
||||
await repository.asyncInitialize(filter);
|
||||
|
||||
// Balance sheet meta first to get date format.
|
||||
const meta = await this.balanceSheetMeta.meta(filter);
|
||||
@@ -46,7 +48,7 @@ export class BalanceSheetInjectable {
|
||||
// Balance sheet report instance.
|
||||
const balanceSheetInstanace = new BalanceSheet(
|
||||
filter,
|
||||
this.balanceSheetRepository,
|
||||
repository,
|
||||
this.i18n,
|
||||
{ baseCurrency: tenantMetadata.baseCurrency, dateFormat: meta.dateFormat },
|
||||
);
|
||||
|
||||
+17
@@ -57,6 +57,11 @@ export class BalanceSheetRepository extends R.compose(
|
||||
*/
|
||||
public accountsByType: any;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public accountsByParentType: any;
|
||||
|
||||
/**
|
||||
* PY from date.
|
||||
* @param {Date}
|
||||
@@ -97,6 +102,18 @@ export class BalanceSheetRepository extends R.compose(
|
||||
*/
|
||||
public expensesLedger: Ledger;
|
||||
|
||||
/**
|
||||
* Income accounts.
|
||||
*/
|
||||
public incomeAccounts: any;
|
||||
public incomeAccountsIds: number[];
|
||||
|
||||
/**
|
||||
* Expense accounts.
|
||||
*/
|
||||
public expenseAccounts: any;
|
||||
public expenseAccountsIds: number[];
|
||||
|
||||
/**
|
||||
* Transactions group type.
|
||||
* @param {IAccountTransactionsGroupBy}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { IBalanceSheetRepository } from './IBalanceSheetRepository';
|
||||
import { BalanceSheetRepository } from './BalanceSheetRepository';
|
||||
import { BalanceSheetClickHouseRepository } from './BalanceSheetClickHouseRepository';
|
||||
|
||||
@Injectable()
|
||||
export class BalanceSheetRepositoryFactory {
|
||||
constructor(
|
||||
@Inject(BalanceSheetRepository)
|
||||
private readonly mysqlRepository: BalanceSheetRepository,
|
||||
@Inject(BalanceSheetClickHouseRepository)
|
||||
private readonly clickHouseRepository: BalanceSheetClickHouseRepository,
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Returns the appropriate balance sheet repository based on configuration.
|
||||
*/
|
||||
getRepository(): IBalanceSheetRepository {
|
||||
const enabled = this.configService.get<boolean>('clickhouse.enabled', false);
|
||||
if (enabled) {
|
||||
return this.clickHouseRepository;
|
||||
}
|
||||
return this.mysqlRepository;
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import { ILedger } from '@/modules/Ledger/types/Ledger.types';
|
||||
import { ModelObject } from 'objection';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { IBalanceSheetQuery } from './BalanceSheet.types';
|
||||
|
||||
/**
|
||||
* Balance sheet repository interface.
|
||||
* Both MySQL and ClickHouse implementations must conform to this interface.
|
||||
*/
|
||||
export interface IBalanceSheetRepository {
|
||||
// Query
|
||||
query: any;
|
||||
|
||||
// Accounts
|
||||
accounts: ModelObject<Account>[];
|
||||
accountsGraph: any;
|
||||
accountsByType: Map<string, ModelObject<Account>[]>;
|
||||
accountsByParentType: Map<string, ModelObject<Account>[]>;
|
||||
|
||||
// Total closing ledger
|
||||
totalAccountsLedger: ILedger;
|
||||
|
||||
// Income / Expense ledgers
|
||||
incomeLedger: ILedger;
|
||||
expensesLedger: ILedger;
|
||||
|
||||
// Date periods
|
||||
periodsAccountsLedger: ILedger;
|
||||
periodsOpeningAccountLedger: ILedger;
|
||||
|
||||
// Previous Year (PY)
|
||||
PYTotalAccountsLedger: ILedger;
|
||||
PYPeriodsAccountsLedger: ILedger;
|
||||
PYPeriodsOpeningAccountLedger: ILedger;
|
||||
|
||||
// Previous Period (PP)
|
||||
PPTotalAccountsLedger: ILedger;
|
||||
PPPeriodsAccountsLedger: ILedger;
|
||||
PPPeriodsOpeningAccountLedger: ILedger;
|
||||
|
||||
// Net Income - Date Periods
|
||||
incomePeriodsAccountsLedger: ILedger;
|
||||
incomePeriodsOpeningAccountsLedger: ILedger;
|
||||
expensesPeriodsAccountsLedger: ILedger;
|
||||
expensesOpeningAccountLedger: ILedger;
|
||||
|
||||
// Net Income - Previous Period
|
||||
incomePPAccountsLedger: ILedger;
|
||||
expensePPAccountsLedger: ILedger;
|
||||
incomePPPeriodsAccountsLedger: ILedger;
|
||||
incomePPPeriodsOpeningAccountLedger: ILedger;
|
||||
expensePPPeriodsAccountsLedger: ILedger;
|
||||
expensePPPeriodsOpeningAccountLedger: ILedger;
|
||||
|
||||
// Net Income - Previous Year
|
||||
incomePYTotalAccountsLedger: ILedger;
|
||||
expensePYTotalAccountsLedger: ILedger;
|
||||
incomePYPeriodsAccountsLedger: ILedger;
|
||||
incomePYPeriodsOpeningAccountLedger: ILedger;
|
||||
expensePYPeriodsAccountsLedger: ILedger;
|
||||
expensePYPeriodsOpeningAccountLedger: ILedger;
|
||||
|
||||
// Income / Expense account lists
|
||||
incomeAccounts: ModelObject<Account>[];
|
||||
incomeAccountsIds: number[];
|
||||
expenseAccounts: ModelObject<Account>[];
|
||||
expenseAccountsIds: number[];
|
||||
|
||||
// Transactions group type
|
||||
transactionsGroupType: string;
|
||||
|
||||
/**
|
||||
* Async initialize the repository with the given query.
|
||||
*/
|
||||
asyncInitialize(query: IBalanceSheetQuery): Promise<void>;
|
||||
}
|
||||
Reference in New Issue
Block a user