mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 07:40:32 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4f61823b3 | ||
|
|
1cbc1c056f | ||
|
|
4d4ef54c56 | ||
|
|
f7fcfefc78 | ||
|
|
858f347fd4 | ||
|
|
4d73b59cf3 | ||
|
|
bc67f0cca8 | ||
|
|
ef2d1ff141 | ||
|
|
dc4cdb2a8f | ||
|
|
8b99e0938d | ||
|
|
94192bfc29 | ||
|
|
708a4dda9e | ||
|
|
10fcf94c92 | ||
|
|
5dbfd36415 | ||
|
|
044f11ff74 | ||
|
|
6afe1a09c6 |
@@ -25,6 +25,7 @@
|
|||||||
"@casl/ability": "^5.4.3",
|
"@casl/ability": "^5.4.3",
|
||||||
"@hapi/boom": "^7.4.3",
|
"@hapi/boom": "^7.4.3",
|
||||||
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
||||||
|
"@supercharge/promise-pool": "^3.2.0",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/i18n": "^0.8.7",
|
"@types/i18n": "^0.8.7",
|
||||||
"@types/knex": "^0.16.1",
|
"@types/knex": "^0.16.1",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import color from 'colorette';
|
|||||||
import argv from 'getopts';
|
import argv from 'getopts';
|
||||||
import Knex from 'knex';
|
import Knex from 'knex';
|
||||||
import { knexSnakeCaseMappers } from 'objection';
|
import { knexSnakeCaseMappers } from 'objection';
|
||||||
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
import '../before';
|
import '../before';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ function initSystemKnex() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initTenantKnex(organizationId) {
|
function initTenantKnex(organizationId: string = '') {
|
||||||
return Knex({
|
return Knex({
|
||||||
client: config.tenant.db_client,
|
client: config.tenant.db_client,
|
||||||
connection: {
|
connection: {
|
||||||
@@ -71,10 +72,12 @@ function getAllSystemTenants(knex) {
|
|||||||
return knex('tenants');
|
return knex('tenants');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllInitializedSystemTenants(knex) {
|
function getAllInitializedTenants(knex) {
|
||||||
return knex('tenants').whereNotNull('initializedAt');
|
return knex('tenants').whereNotNull('initializedAt');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MIGRATION_CONCURRENCY = 10;
|
||||||
|
|
||||||
// module.exports = {
|
// module.exports = {
|
||||||
// log,
|
// log,
|
||||||
// success,
|
// success,
|
||||||
@@ -91,6 +94,7 @@ function getAllInitializedSystemTenants(knex) {
|
|||||||
// - bigcapital tenants:migrate:make
|
// - bigcapital tenants:migrate:make
|
||||||
// - bigcapital system:migrate:make
|
// - bigcapital system:migrate:make
|
||||||
// - bigcapital tenants:list
|
// - bigcapital tenants:list
|
||||||
|
// - bigcapital tenants:list --all
|
||||||
|
|
||||||
commander
|
commander
|
||||||
.command('system:migrate:rollback')
|
.command('system:migrate:rollback')
|
||||||
@@ -149,10 +153,13 @@ commander
|
|||||||
commander
|
commander
|
||||||
.command('tenants:list')
|
.command('tenants:list')
|
||||||
.description('Retrieve a list of all system tenants databases.')
|
.description('Retrieve a list of all system tenants databases.')
|
||||||
|
.option('-a, --all', 'All tenants even are not initialized.')
|
||||||
.action(async (cmd) => {
|
.action(async (cmd) => {
|
||||||
try {
|
try {
|
||||||
const sysKnex = await initSystemKnex();
|
const sysKnex = await initSystemKnex();
|
||||||
const tenants = await getAllSystemTenants(sysKnex);
|
const tenants = cmd?.all
|
||||||
|
? await getAllSystemTenants(sysKnex)
|
||||||
|
: await getAllInitializedTenants(sysKnex);
|
||||||
|
|
||||||
tenants.forEach((tenant) => {
|
tenants.forEach((tenant) => {
|
||||||
const dbName = `${config.tenant.db_name_prefix}${tenant.organizationId}`;
|
const dbName = `${config.tenant.db_name_prefix}${tenant.organizationId}`;
|
||||||
@@ -183,18 +190,20 @@ commander
|
|||||||
commander
|
commander
|
||||||
.command('tenants:migrate:latest')
|
.command('tenants:migrate:latest')
|
||||||
.description('Migrate all tenants or the given tenant id.')
|
.description('Migrate all tenants or the given tenant id.')
|
||||||
.option('-t, --tenant_id [tenant_id]', 'Which tenant id do you migrate.')
|
.option(
|
||||||
|
'-t, --tenant_id [tenant_id]',
|
||||||
|
'Which organization id do you migrate.'
|
||||||
|
)
|
||||||
.action(async (cmd) => {
|
.action(async (cmd) => {
|
||||||
try {
|
try {
|
||||||
const sysKnex = await initSystemKnex();
|
const sysKnex = await initSystemKnex();
|
||||||
const tenants = await getAllInitializedSystemTenants(sysKnex);
|
const tenants = await getAllInitializedTenants(sysKnex);
|
||||||
const tenantsOrgsIds = tenants.map((tenant) => tenant.organizationId);
|
const tenantsOrgsIds = tenants.map((tenant) => tenant.organizationId);
|
||||||
|
|
||||||
if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) {
|
if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) {
|
||||||
exit(`The given tenant id ${cmd.tenant_id} is not exists.`);
|
exit(`The given tenant id ${cmd.tenant_id} is not exists.`);
|
||||||
}
|
}
|
||||||
// Validate the tenant id exist first of all.
|
// Validate the tenant id exist first of all.
|
||||||
const migrateOpers = [];
|
|
||||||
const migrateTenant = async (organizationId) => {
|
const migrateTenant = async (organizationId) => {
|
||||||
try {
|
try {
|
||||||
const tenantKnex = await initTenantKnex(organizationId);
|
const tenantKnex = await initTenantKnex(organizationId);
|
||||||
@@ -216,17 +225,17 @@ commander
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (!cmd.tenant_id) {
|
if (!cmd.tenant_id) {
|
||||||
tenants.forEach((tenant) => {
|
await PromisePool.withConcurrency(MIGRATION_CONCURRENCY)
|
||||||
const oper = migrateTenant(tenant.organizationId);
|
.for(tenants)
|
||||||
migrateOpers.push(oper);
|
.process((tenant, index, pool) => {
|
||||||
});
|
return migrateTenant(tenant.organizationId);
|
||||||
} else {
|
})
|
||||||
const oper = migrateTenant(cmd.tenant_id);
|
.then(() => {
|
||||||
migrateOpers.push(oper);
|
|
||||||
}
|
|
||||||
Promise.all(migrateOpers).then(() => {
|
|
||||||
success('All tenants are migrated.');
|
success('All tenants are migrated.');
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
await migrateTenant(cmd.tenant_id);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
exit(error);
|
exit(error);
|
||||||
}
|
}
|
||||||
@@ -235,19 +244,21 @@ commander
|
|||||||
commander
|
commander
|
||||||
.command('tenants:migrate:rollback')
|
.command('tenants:migrate:rollback')
|
||||||
.description('Rollback the last batch of tenants migrations.')
|
.description('Rollback the last batch of tenants migrations.')
|
||||||
.option('-t, --tenant_id [tenant_id]', 'Which tenant id do you migrate.')
|
.option(
|
||||||
|
'-t, --tenant_id [tenant_id]',
|
||||||
|
'Which organization id do you migrate.'
|
||||||
|
)
|
||||||
.action(async (cmd) => {
|
.action(async (cmd) => {
|
||||||
try {
|
try {
|
||||||
const sysKnex = await initSystemKnex();
|
const sysKnex = await initSystemKnex();
|
||||||
const tenants = await getAllSystemTenants(sysKnex);
|
const tenants = await getAllInitializedTenants(sysKnex);
|
||||||
const tenantsOrgsIds = tenants.map((tenant) => tenant.organizationId);
|
const tenantsOrgsIds = tenants.map((tenant) => tenant.organizationId);
|
||||||
|
|
||||||
if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) {
|
if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) {
|
||||||
exit(`The given tenant id ${cmd.tenant_id} is not exists.`);
|
exit(`The given tenant id ${cmd.tenant_id} is not exists.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const migrateOpers = [];
|
const migrateTenant = async (organizationId: string) => {
|
||||||
const migrateTenant = async (organizationId) => {
|
|
||||||
try {
|
try {
|
||||||
const tenantKnex = await initTenantKnex(organizationId);
|
const tenantKnex = await initTenantKnex(organizationId);
|
||||||
const [batchNo, _log] = await tenantKnex.migrate.rollback();
|
const [batchNo, _log] = await tenantKnex.migrate.rollback();
|
||||||
@@ -268,17 +279,17 @@ commander
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!cmd.tenant_id) {
|
if (!cmd.tenant_id) {
|
||||||
tenants.forEach((tenant) => {
|
await PromisePool.withConcurrency(MIGRATION_CONCURRENCY)
|
||||||
const oper = migrateTenant(tenant.organizationId);
|
.for(tenants)
|
||||||
migrateOpers.push(oper);
|
.process((tenant, index, pool) => {
|
||||||
});
|
return migrateTenant(tenant.organizationId);
|
||||||
} else {
|
})
|
||||||
const oper = migrateTenant(cmd.tenant_id);
|
.then(() => {
|
||||||
migrateOpers.push(oper);
|
|
||||||
}
|
|
||||||
Promise.all(migrateOpers).then(() => {
|
|
||||||
success('All tenants are rollbacked.');
|
success('All tenants are rollbacked.');
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
await migrateTenant(cmd.tenant_id);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
exit(error);
|
exit(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ export interface IGeneralLedgerSheetAccount {
|
|||||||
transactions: IGeneralLedgerSheetAccountTransaction[];
|
transactions: IGeneralLedgerSheetAccountTransaction[];
|
||||||
openingBalance: IGeneralLedgerSheetAccountBalance;
|
openingBalance: IGeneralLedgerSheetAccountBalance;
|
||||||
closingBalance: IGeneralLedgerSheetAccountBalance;
|
closingBalance: IGeneralLedgerSheetAccountBalance;
|
||||||
|
closingBalanceSubaccounts?: IGeneralLedgerSheetAccountBalance;
|
||||||
|
children?: IGeneralLedgerSheetAccount[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IGeneralLedgerSheetData = IGeneralLedgerSheetAccount[];
|
export type IGeneralLedgerSheetData = IGeneralLedgerSheetAccount[];
|
||||||
|
|||||||
@@ -104,10 +104,10 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
|||||||
*/
|
*/
|
||||||
private async updateUncategorizedTransactionCount(
|
private async updateUncategorizedTransactionCount(
|
||||||
queryContext: QueryContext,
|
queryContext: QueryContext,
|
||||||
increment: boolean
|
increment: boolean,
|
||||||
|
amount: number = 1
|
||||||
) {
|
) {
|
||||||
const operation = increment ? 'increment' : 'decrement';
|
const operation = increment ? 'increment' : 'decrement';
|
||||||
const amount = increment ? 1 : -1;
|
|
||||||
|
|
||||||
await Account.query(queryContext.transaction)
|
await Account.query(queryContext.transaction)
|
||||||
.findById(this.accountId)
|
.findById(this.accountId)
|
||||||
|
|||||||
@@ -274,4 +274,14 @@ export default class Ledger implements ILedger {
|
|||||||
const entries = Ledger.mappingTransactions(transactions);
|
const entries = Ledger.mappingTransactions(transactions);
|
||||||
return new Ledger(entries);
|
return new Ledger(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the transaction amount.
|
||||||
|
* @param {number} credit - Credit amount.
|
||||||
|
* @param {number} debit - Debit amount.
|
||||||
|
* @param {string} normal - Credit or debit.
|
||||||
|
*/
|
||||||
|
static getAmount(credit: number, debit: number, normal: string) {
|
||||||
|
return normal === 'credit' ? credit - debit : debit - credit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { Knex } from 'knex';
|
|||||||
import { transformCategorizeTransToCashflow } from './utils';
|
import { transformCategorizeTransToCashflow } from './utils';
|
||||||
import { CommandCashflowValidator } from './CommandCasflowValidator';
|
import { CommandCashflowValidator } from './CommandCasflowValidator';
|
||||||
import NewCashflowTransactionService from './NewCashflowTransactionService';
|
import NewCashflowTransactionService from './NewCashflowTransactionService';
|
||||||
import { TransferAuthorizationGuaranteeDecision } from 'plaid';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CategorizeCashflowTransaction {
|
export class CategorizeCashflowTransaction {
|
||||||
|
|||||||
@@ -68,7 +68,11 @@ export const CASHFLOW_TRANSACTION_TYPE_META = {
|
|||||||
[`${CASHFLOW_TRANSACTION_TYPE.OTHER_EXPENSE}`]: {
|
[`${CASHFLOW_TRANSACTION_TYPE.OTHER_EXPENSE}`]: {
|
||||||
type: 'OtherExpense',
|
type: 'OtherExpense',
|
||||||
direction: CASHFLOW_DIRECTION.OUT,
|
direction: CASHFLOW_DIRECTION.OUT,
|
||||||
creditType: [ACCOUNT_TYPE.EXPENSE, ACCOUNT_TYPE.OTHER_EXPENSE],
|
creditType: [
|
||||||
|
ACCOUNT_TYPE.EXPENSE,
|
||||||
|
ACCOUNT_TYPE.OTHER_EXPENSE,
|
||||||
|
ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { upperFirst, camelCase, omit } from 'lodash';
|
import { upperFirst, camelCase } from 'lodash';
|
||||||
import {
|
import {
|
||||||
CASHFLOW_TRANSACTION_TYPE,
|
CASHFLOW_TRANSACTION_TYPE,
|
||||||
CASHFLOW_TRANSACTION_TYPE_META,
|
CASHFLOW_TRANSACTION_TYPE_META,
|
||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
} from './constants';
|
} from './constants';
|
||||||
import {
|
import {
|
||||||
ICashflowNewCommandDTO,
|
ICashflowNewCommandDTO,
|
||||||
ICashflowTransaction,
|
|
||||||
ICategorizeCashflowTransactioDTO,
|
ICategorizeCashflowTransactioDTO,
|
||||||
IUncategorizedCashflowTransaction,
|
IUncategorizedCashflowTransaction,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
@@ -62,6 +61,7 @@ export const transformCategorizeTransToCashflow = (
|
|||||||
transactionNumber: categorizeDTO.transactionNumber,
|
transactionNumber: categorizeDTO.transactionNumber,
|
||||||
transactionType: categorizeDTO.transactionType,
|
transactionType: categorizeDTO.transactionType,
|
||||||
uncategorizedTransactionId: uncategorizeModel.id,
|
uncategorizedTransactionId: uncategorizeModel.id,
|
||||||
|
branchId: categorizeDTO?.branchId,
|
||||||
publish: true,
|
publish: true,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { FinancialPreviousPeriod } from '../FinancialPreviousPeriod';
|
|
||||||
import { FinancialHorizTotals } from '../FinancialHorizTotals';
|
|
||||||
import { FinancialSheetStructure } from '../FinancialSheetStructure';
|
import { FinancialSheetStructure } from '../FinancialSheetStructure';
|
||||||
import {
|
import {
|
||||||
BALANCE_SHEET_SCHEMA_NODE_TYPE,
|
BALANCE_SHEET_SCHEMA_NODE_TYPE,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import * as R from 'ramda';
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import {
|
import {
|
||||||
IAccount,
|
|
||||||
IAccountTransactionsGroupBy,
|
IAccountTransactionsGroupBy,
|
||||||
IBalanceSheetQuery,
|
IBalanceSheetQuery,
|
||||||
ILedger,
|
ILedger,
|
||||||
@@ -12,7 +11,6 @@ import { transformToMapBy } from 'utils';
|
|||||||
import Ledger from '@/services/Accounting/Ledger';
|
import Ledger from '@/services/Accounting/Ledger';
|
||||||
import { BalanceSheetQuery } from './BalanceSheetQuery';
|
import { BalanceSheetQuery } from './BalanceSheetQuery';
|
||||||
import { FinancialDatePeriods } from '../FinancialDatePeriods';
|
import { FinancialDatePeriods } from '../FinancialDatePeriods';
|
||||||
import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from '@/data/AccountTypes';
|
|
||||||
import { BalanceSheetRepositoryNetIncome } from './BalanceSheetRepositoryNetIncome';
|
import { BalanceSheetRepositoryNetIncome } from './BalanceSheetRepositoryNetIncome';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
import { isEmpty, get, last, sumBy } from 'lodash';
|
import { isEmpty, get, last, sumBy, first, head } from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
IGeneralLedgerSheetQuery,
|
IGeneralLedgerSheetQuery,
|
||||||
IGeneralLedgerSheetAccount,
|
IGeneralLedgerSheetAccount,
|
||||||
IGeneralLedgerSheetAccountBalance,
|
IGeneralLedgerSheetAccountBalance,
|
||||||
IGeneralLedgerSheetAccountTransaction,
|
IGeneralLedgerSheetAccountTransaction,
|
||||||
IAccount,
|
IAccount,
|
||||||
IJournalPoster,
|
ILedgerEntry,
|
||||||
IJournalEntry,
|
|
||||||
IContact,
|
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import FinancialSheet from '../FinancialSheet';
|
import FinancialSheet from '../FinancialSheet';
|
||||||
import moment from 'moment';
|
import { GeneralLedgerRepository } from './GeneralLedgerRepository';
|
||||||
|
import { FinancialSheetStructure } from '../FinancialSheetStructure';
|
||||||
|
import { flatToNestedArray } from '@/utils';
|
||||||
|
import Ledger from '@/services/Accounting/Ledger';
|
||||||
|
import { calculateRunningBalance } from './_utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General ledger sheet.
|
* General ledger sheet.
|
||||||
*/
|
*/
|
||||||
export default class GeneralLedgerSheet extends FinancialSheet {
|
export default class GeneralLedgerSheet extends R.compose(
|
||||||
tenantId: number;
|
FinancialSheetStructure
|
||||||
accounts: IAccount[];
|
)(FinancialSheet) {
|
||||||
query: IGeneralLedgerSheetQuery;
|
private query: IGeneralLedgerSheetQuery;
|
||||||
openingBalancesJournal: IJournalPoster;
|
private baseCurrency: string;
|
||||||
transactions: IJournalPoster;
|
private i18n: any;
|
||||||
contactsMap: Map<number, IContact>;
|
private repository: GeneralLedgerRepository;
|
||||||
baseCurrency: string;
|
|
||||||
i18n: any;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
@@ -34,63 +36,59 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
* @param {IJournalPoster} closingBalancesJournal -
|
* @param {IJournalPoster} closingBalancesJournal -
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
tenantId: number,
|
|
||||||
query: IGeneralLedgerSheetQuery,
|
query: IGeneralLedgerSheetQuery,
|
||||||
accounts: IAccount[],
|
repository: GeneralLedgerRepository,
|
||||||
contactsByIdMap: Map<number, IContact>,
|
|
||||||
transactions: IJournalPoster,
|
|
||||||
openingBalancesJournal: IJournalPoster,
|
|
||||||
baseCurrency: string,
|
|
||||||
i18n
|
i18n
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.tenantId = tenantId;
|
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.numberFormat = this.query.numberFormat;
|
this.numberFormat = this.query.numberFormat;
|
||||||
this.accounts = accounts;
|
this.repository = repository;
|
||||||
this.contactsMap = contactsByIdMap;
|
this.baseCurrency = this.repository.tenant.metadata.currencyCode;
|
||||||
this.transactions = transactions;
|
|
||||||
this.openingBalancesJournal = openingBalancesJournal;
|
|
||||||
this.baseCurrency = baseCurrency;
|
|
||||||
this.i18n = i18n;
|
this.i18n = i18n;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the transaction amount.
|
|
||||||
* @param {number} credit - Credit amount.
|
|
||||||
* @param {number} debit - Debit amount.
|
|
||||||
* @param {string} normal - Credit or debit.
|
|
||||||
*/
|
|
||||||
getAmount(credit: number, debit: number, normal: string) {
|
|
||||||
return normal === 'credit' ? credit - debit : debit - credit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry mapper.
|
* Entry mapper.
|
||||||
* @param {IJournalEntry} entry -
|
* @param {ILedgerEntry} entry -
|
||||||
* @return {IGeneralLedgerSheetAccountTransaction}
|
* @return {IGeneralLedgerSheetAccountTransaction}
|
||||||
*/
|
*/
|
||||||
entryReducer(
|
private getEntryRunningBalance(
|
||||||
entries: IGeneralLedgerSheetAccountTransaction[],
|
entry: ILedgerEntry,
|
||||||
entry: IJournalEntry,
|
openingBalance: number,
|
||||||
openingBalance: number
|
runningBalance?: number
|
||||||
): IGeneralLedgerSheetAccountTransaction[] {
|
): number {
|
||||||
const lastEntry = last(entries);
|
const lastRunningBalance = runningBalance || openingBalance;
|
||||||
|
|
||||||
const contact = this.contactsMap.get(entry.contactId);
|
const amount = Ledger.getAmount(
|
||||||
const amount = this.getAmount(
|
|
||||||
entry.credit,
|
entry.credit,
|
||||||
entry.debit,
|
entry.debit,
|
||||||
entry.accountNormal
|
entry.accountNormal
|
||||||
);
|
);
|
||||||
const runningBalance =
|
return calculateRunningBalance(amount, lastRunningBalance);
|
||||||
amount + (!isEmpty(entries) ? lastEntry.runningBalance : openingBalance);
|
}
|
||||||
|
|
||||||
const newEntry = {
|
/**
|
||||||
|
* Maps the given ledger entry to G/L transaction.
|
||||||
|
* @param {ILedgerEntry} entry
|
||||||
|
* @param {number} runningBalance
|
||||||
|
* @returns {IGeneralLedgerSheetAccountTransaction}
|
||||||
|
*/
|
||||||
|
private transactionMapper(
|
||||||
|
entry: ILedgerEntry,
|
||||||
|
runningBalance: number
|
||||||
|
): IGeneralLedgerSheetAccountTransaction {
|
||||||
|
const contact = this.repository.contactsById.get(entry.contactId);
|
||||||
|
const amount = Ledger.getAmount(
|
||||||
|
entry.credit,
|
||||||
|
entry.debit,
|
||||||
|
entry.accountNormal
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
id: entry.id,
|
||||||
date: entry.date,
|
date: entry.date,
|
||||||
dateFormatted: moment(entry.date).format('YYYY MMM DD'),
|
dateFormatted: moment(entry.date).format('YYYY MMM DD'),
|
||||||
entryId: entry.id,
|
|
||||||
|
|
||||||
transactionNumber: entry.transactionNumber,
|
transactionNumber: entry.transactionNumber,
|
||||||
referenceType: entry.referenceType,
|
referenceType: entry.referenceType,
|
||||||
@@ -109,16 +107,15 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
amount,
|
amount,
|
||||||
runningBalance,
|
runningBalance,
|
||||||
|
|
||||||
formattedAmount: this.formatNumber(amount),
|
formattedAmount: this.formatNumber(amount, { excerptZero: false }),
|
||||||
formattedCredit: this.formatNumber(entry.credit),
|
formattedCredit: this.formatNumber(entry.credit, { excerptZero: false }),
|
||||||
formattedDebit: this.formatNumber(entry.debit),
|
formattedDebit: this.formatNumber(entry.debit, { excerptZero: false }),
|
||||||
formattedRunningBalance: this.formatNumber(runningBalance),
|
formattedRunningBalance: this.formatNumber(runningBalance, {
|
||||||
|
excerptZero: false,
|
||||||
|
}),
|
||||||
|
|
||||||
currencyCode: this.baseCurrency,
|
currencyCode: this.baseCurrency,
|
||||||
};
|
} as IGeneralLedgerSheetAccountTransaction;
|
||||||
entries.push(newEntry);
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,28 +127,48 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
account: IAccount,
|
account: IAccount,
|
||||||
openingBalance: number
|
openingBalance: number
|
||||||
): IGeneralLedgerSheetAccountTransaction[] {
|
): IGeneralLedgerSheetAccountTransaction[] {
|
||||||
const entries = this.transactions.getAccountEntries(account.id);
|
const entries = this.repository.transactionsLedger
|
||||||
|
.whereAccountId(account.id)
|
||||||
|
.getEntries();
|
||||||
|
|
||||||
return entries.reduce(
|
return entries
|
||||||
(
|
.reduce((prev: Array<[number, ILedgerEntry]>, current: ILedgerEntry) => {
|
||||||
entries: IGeneralLedgerSheetAccountTransaction[],
|
const prevEntry = last(prev);
|
||||||
entry: IJournalEntry
|
const prevRunningBalance = head(prevEntry) as number;
|
||||||
) => {
|
const amount = this.getEntryRunningBalance(
|
||||||
return this.entryReducer(entries, entry, openingBalance);
|
current,
|
||||||
},
|
openingBalance,
|
||||||
[]
|
prevRunningBalance
|
||||||
);
|
);
|
||||||
|
return [...prev, [amount, current]];
|
||||||
|
}, [])
|
||||||
|
.map((entryPair: [number, ILedgerEntry]) => {
|
||||||
|
const [runningBalance, entry] = entryPair;
|
||||||
|
|
||||||
|
return this.transactionMapper(entry, runningBalance);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve account opening balance.
|
* Retrieves the given account opening balance.
|
||||||
|
* @param {number} accountId
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
private accountOpeningBalance(accountId: number): number {
|
||||||
|
return this.repository.openingBalanceTransactionsLedger
|
||||||
|
.whereAccountId(accountId)
|
||||||
|
.getClosingBalance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the given account opening balance.
|
||||||
* @param {IAccount} account
|
* @param {IAccount} account
|
||||||
* @return {IGeneralLedgerSheetAccountBalance}
|
* @return {IGeneralLedgerSheetAccountBalance}
|
||||||
*/
|
*/
|
||||||
private accountOpeningBalance(
|
private accountOpeningBalanceTotal(
|
||||||
account: IAccount
|
accountId: number
|
||||||
): IGeneralLedgerSheetAccountBalance {
|
): IGeneralLedgerSheetAccountBalance {
|
||||||
const amount = this.openingBalancesJournal.getAccountBalance(account.id);
|
const amount = this.accountOpeningBalance(accountId);
|
||||||
const formattedAmount = this.formatTotalNumber(amount);
|
const formattedAmount = this.formatTotalNumber(amount);
|
||||||
const currencyCode = this.baseCurrency;
|
const currencyCode = this.baseCurrency;
|
||||||
const date = this.query.fromDate;
|
const date = this.query.fromDate;
|
||||||
@@ -160,15 +177,31 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve account closing balance.
|
* Retrieves the given account closing balance.
|
||||||
|
* @param {number} accountId
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
private accountClosingBalance(accountId: number): number {
|
||||||
|
const openingBalance = this.repository.openingBalanceTransactionsLedger
|
||||||
|
.whereAccountId(accountId)
|
||||||
|
.getClosingBalance();
|
||||||
|
|
||||||
|
const transactionsBalance = this.repository.transactionsLedger
|
||||||
|
.whereAccountId(accountId)
|
||||||
|
.getClosingBalance();
|
||||||
|
|
||||||
|
return openingBalance + transactionsBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the given account closing balance.
|
||||||
* @param {IAccount} account
|
* @param {IAccount} account
|
||||||
* @return {IGeneralLedgerSheetAccountBalance}
|
* @return {IGeneralLedgerSheetAccountBalance}
|
||||||
*/
|
*/
|
||||||
private accountClosingBalance(
|
private accountClosingBalanceTotal(
|
||||||
openingBalance: number,
|
accountId: number
|
||||||
transactions: IGeneralLedgerSheetAccountTransaction[]
|
|
||||||
): IGeneralLedgerSheetAccountBalance {
|
): IGeneralLedgerSheetAccountBalance {
|
||||||
const amount = this.calcClosingBalance(openingBalance, transactions);
|
const amount = this.accountClosingBalance(accountId);
|
||||||
const formattedAmount = this.formatTotalNumber(amount);
|
const formattedAmount = this.formatTotalNumber(amount);
|
||||||
const currencyCode = this.baseCurrency;
|
const currencyCode = this.baseCurrency;
|
||||||
const date = this.query.toDate;
|
const date = this.query.toDate;
|
||||||
@@ -176,31 +209,78 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
return { amount, formattedAmount, currencyCode, date };
|
return { amount, formattedAmount, currencyCode, date };
|
||||||
}
|
}
|
||||||
|
|
||||||
private calcClosingBalance(
|
/**
|
||||||
openingBalance: number,
|
* Retrieves the given account closing balance with subaccounts.
|
||||||
transactions: IGeneralLedgerSheetAccountTransaction[]
|
* @param {number} accountId
|
||||||
) {
|
* @returns {number}
|
||||||
return openingBalance + sumBy(transactions, (trans) => trans.amount);
|
*/
|
||||||
|
private accountClosingBalanceWithSubaccounts = (
|
||||||
|
accountId: number
|
||||||
|
): number => {
|
||||||
|
const depsAccountsIds =
|
||||||
|
this.repository.accountsGraph.dependenciesOf(accountId);
|
||||||
|
|
||||||
|
const openingBalance = this.repository.openingBalanceTransactionsLedger
|
||||||
|
.whereAccountsIds([...depsAccountsIds, accountId])
|
||||||
|
.getClosingBalance();
|
||||||
|
|
||||||
|
const transactionsBalanceWithSubAccounts =
|
||||||
|
this.repository.transactionsLedger
|
||||||
|
.whereAccountsIds([...depsAccountsIds, accountId])
|
||||||
|
.getClosingBalance();
|
||||||
|
|
||||||
|
const closingBalance = openingBalance + transactionsBalanceWithSubAccounts;
|
||||||
|
|
||||||
|
return closingBalance;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the closing balance with subaccounts total node.
|
||||||
|
* @param {number} accountId
|
||||||
|
* @returns {IGeneralLedgerSheetAccountBalance}
|
||||||
|
*/
|
||||||
|
private accountClosingBalanceWithSubaccountsTotal = (
|
||||||
|
accountId: number
|
||||||
|
): IGeneralLedgerSheetAccountBalance => {
|
||||||
|
const amount = this.accountClosingBalanceWithSubaccounts(accountId);
|
||||||
|
const formattedAmount = this.formatTotalNumber(amount);
|
||||||
|
const currencyCode = this.baseCurrency;
|
||||||
|
const date = this.query.toDate;
|
||||||
|
|
||||||
|
return { amount, formattedAmount, currencyCode, date };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the closing balance subaccounts node should be exist.
|
||||||
|
* @param {number} accountId
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
private isAccountNodeIncludesClosingSubaccounts = (accountId: number) => {
|
||||||
|
// Retrun early if there is no accounts in the filter so
|
||||||
|
// return closing subaccounts in all cases.
|
||||||
|
if (isEmpty(this.query.accountsIds)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
// Returns true if the given account id includes transactions.
|
||||||
|
return this.repository.accountNodesIncludeTransactions.includes(accountId);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retreive general ledger accounts sections.
|
* Retreive general ledger accounts sections.
|
||||||
* @param {IAccount} account
|
* @param {IAccount} account
|
||||||
* @return {IGeneralLedgerSheetAccount}
|
* @return {IGeneralLedgerSheetAccount}
|
||||||
*/
|
*/
|
||||||
private accountMapper(account: IAccount): IGeneralLedgerSheetAccount {
|
private accountMapper = (account: IAccount): IGeneralLedgerSheetAccount => {
|
||||||
const openingBalance = this.accountOpeningBalance(account);
|
const openingBalance = this.accountOpeningBalanceTotal(account.id);
|
||||||
|
|
||||||
const transactions = this.accountTransactionsMapper(
|
const transactions = this.accountTransactionsMapper(
|
||||||
account,
|
account,
|
||||||
openingBalance.amount
|
openingBalance.amount
|
||||||
);
|
);
|
||||||
const closingBalance = this.accountClosingBalance(
|
const closingBalance = this.accountClosingBalanceTotal(account.id);
|
||||||
openingBalance.amount,
|
const closingBalanceSubaccounts =
|
||||||
transactions
|
this.accountClosingBalanceWithSubaccountsTotal(account.id);
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
const initialNode = {
|
||||||
id: account.id,
|
id: account.id,
|
||||||
name: account.name,
|
name: account.name,
|
||||||
code: account.code,
|
code: account.code,
|
||||||
@@ -210,34 +290,90 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
transactions,
|
transactions,
|
||||||
closingBalance,
|
closingBalance,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
() => this.isAccountNodeIncludesClosingSubaccounts(account.id),
|
||||||
|
R.assoc('closingBalanceSubaccounts', closingBalanceSubaccounts)
|
||||||
|
)
|
||||||
|
)(initialNode);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve mapped accounts with general ledger transactions and opeing/closing balance.
|
* Maps over deep nodes to retrieve the G/L account node.
|
||||||
|
* @param {IAccount[]} accounts
|
||||||
|
* @returns {IGeneralLedgerSheetAccount[]}
|
||||||
|
*/
|
||||||
|
private accountNodesDeepMap = (
|
||||||
|
accounts: IAccount[]
|
||||||
|
): IGeneralLedgerSheetAccount[] => {
|
||||||
|
return this.mapNodesDeep(accounts, this.accountMapper);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the flatten nodes to nested nodes.
|
||||||
|
*/
|
||||||
|
private nestedAccountsNode = (flattenAccounts: IAccount[]): IAccount[] => {
|
||||||
|
return flatToNestedArray(flattenAccounts, {
|
||||||
|
id: 'id',
|
||||||
|
parentId: 'parentAccountId',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters account nodes.
|
||||||
|
* @param {IGeneralLedgerSheetAccount[]} nodes
|
||||||
|
* @returns {IGeneralLedgerSheetAccount[]}
|
||||||
|
*/
|
||||||
|
private filterAccountNodesByTransactionsFilter = (
|
||||||
|
nodes: IGeneralLedgerSheetAccount[]
|
||||||
|
): IGeneralLedgerSheetAccount[] => {
|
||||||
|
return this.filterNodesDeep(
|
||||||
|
nodes,
|
||||||
|
(account: IGeneralLedgerSheetAccount) =>
|
||||||
|
!(account.transactions.length === 0 && this.query.noneTransactions)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters account nodes by the acounts filter.
|
||||||
|
* @param {IAccount[]} nodes
|
||||||
|
* @returns {IAccount[]}
|
||||||
|
*/
|
||||||
|
private filterAccountNodesByAccountsFilter = (
|
||||||
|
nodes: IAccount[]
|
||||||
|
): IAccount[] => {
|
||||||
|
return this.filterNodesDeep(nodes, (node: IGeneralLedgerSheetAccount) => {
|
||||||
|
if (R.isEmpty(this.query.accountsIds)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Returns true if the given account id exists in the filter.
|
||||||
|
return this.repository.accountNodeInclude?.includes(node.id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves mapped accounts with general ledger transactions and
|
||||||
|
* opeing/closing balance.
|
||||||
* @param {IAccount[]} accounts -
|
* @param {IAccount[]} accounts -
|
||||||
* @return {IGeneralLedgerSheetAccount[]}
|
* @return {IGeneralLedgerSheetAccount[]}
|
||||||
*/
|
*/
|
||||||
private accountsWalker(accounts: IAccount[]): IGeneralLedgerSheetAccount[] {
|
private accountsWalker(accounts: IAccount[]): IGeneralLedgerSheetAccount[] {
|
||||||
return (
|
return R.compose(
|
||||||
accounts
|
R.defaultTo([]),
|
||||||
.map((account: IAccount) => this.accountMapper(account))
|
this.filterAccountNodesByTransactionsFilter,
|
||||||
// Filter general ledger accounts that have no transactions
|
this.accountNodesDeepMap,
|
||||||
// when`noneTransactions` is on.
|
R.defaultTo([]),
|
||||||
.filter(
|
this.filterAccountNodesByAccountsFilter,
|
||||||
(generalLedgerAccount: IGeneralLedgerSheetAccount) =>
|
this.nestedAccountsNode
|
||||||
!(
|
)(accounts);
|
||||||
generalLedgerAccount.transactions.length === 0 &&
|
|
||||||
this.query.noneTransactions
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve general ledger report data.
|
* Retrieves general ledger report data.
|
||||||
* @return {IGeneralLedgerSheetAccount[]}
|
* @return {IGeneralLedgerSheetAccount[]}
|
||||||
*/
|
*/
|
||||||
public reportData(): IGeneralLedgerSheetAccount[] {
|
public reportData(): IGeneralLedgerSheetAccount[] {
|
||||||
return this.accountsWalker(this.accounts);
|
return this.accountsWalker(this.repository.accounts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import {
|
||||||
|
IAccount,
|
||||||
|
IAccountTransaction,
|
||||||
|
IContact,
|
||||||
|
IGeneralLedgerSheetQuery,
|
||||||
|
ITenant,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import Ledger from '@/services/Accounting/Ledger';
|
||||||
|
import { transformToMap } from '@/utils';
|
||||||
|
import { Tenant } from '@/system/models';
|
||||||
|
import { flatten, isEmpty, uniq } from 'lodash';
|
||||||
|
|
||||||
|
export class GeneralLedgerRepository {
|
||||||
|
public filter: IGeneralLedgerSheetQuery;
|
||||||
|
public accounts: IAccount[];
|
||||||
|
|
||||||
|
public transactions: IAccountTransaction[];
|
||||||
|
public openingBalanceTransactions: IAccountTransaction[];
|
||||||
|
|
||||||
|
public transactionsLedger: Ledger;
|
||||||
|
public openingBalanceTransactionsLedger: Ledger;
|
||||||
|
|
||||||
|
public repositories: any;
|
||||||
|
public models: any;
|
||||||
|
public accountsGraph: any;
|
||||||
|
|
||||||
|
public contacts: IContact;
|
||||||
|
public contactsById: Map<number, IContact>;
|
||||||
|
|
||||||
|
public tenantId: number;
|
||||||
|
public tenant: ITenant;
|
||||||
|
|
||||||
|
public accountNodesIncludeTransactions: Array<number> = [];
|
||||||
|
public accountNodeInclude: Array<number> = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param models
|
||||||
|
* @param repositories
|
||||||
|
* @param filter
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
repositories: any,
|
||||||
|
filter: IGeneralLedgerSheetQuery,
|
||||||
|
tenantId: number
|
||||||
|
) {
|
||||||
|
this.filter = filter;
|
||||||
|
this.repositories = repositories;
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the G/L report.
|
||||||
|
*/
|
||||||
|
public async asyncInitialize() {
|
||||||
|
await this.initTenant();
|
||||||
|
await this.initAccounts();
|
||||||
|
await this.initAccountsGraph();
|
||||||
|
await this.initContacts();
|
||||||
|
await this.initAccountsOpeningBalance();
|
||||||
|
this.initAccountNodesIncludeTransactions();
|
||||||
|
await this.initTransactions();
|
||||||
|
this.initAccountNodesIncluded();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the tenant.
|
||||||
|
*/
|
||||||
|
public async initTenant() {
|
||||||
|
this.tenant = await Tenant.query()
|
||||||
|
.findById(this.tenantId)
|
||||||
|
.withGraphFetched('metadata');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the accounts.
|
||||||
|
*/
|
||||||
|
public async initAccounts() {
|
||||||
|
this.accounts = await this.repositories.accountRepository
|
||||||
|
.all()
|
||||||
|
.orderBy('name', 'ASC');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the accounts graph.
|
||||||
|
*/
|
||||||
|
public async initAccountsGraph() {
|
||||||
|
this.accountsGraph =
|
||||||
|
await this.repositories.accountRepository.getDependencyGraph();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the contacts.
|
||||||
|
*/
|
||||||
|
public async initContacts() {
|
||||||
|
this.contacts = await this.repositories.contactRepository.all();
|
||||||
|
this.contactsById = transformToMap(this.contacts, 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the G/L transactions from/to the given date.
|
||||||
|
*/
|
||||||
|
public async initTransactions() {
|
||||||
|
this.transactions = await this.repositories.transactionsRepository
|
||||||
|
.journal({
|
||||||
|
fromDate: this.filter.fromDate,
|
||||||
|
toDate: this.filter.toDate,
|
||||||
|
branchesIds: this.filter.branchesIds,
|
||||||
|
})
|
||||||
|
.orderBy('date', 'ASC')
|
||||||
|
.onBuild((query) => {
|
||||||
|
if (this.filter.accountsIds?.length > 0) {
|
||||||
|
query.whereIn('accountId', this.accountNodesIncludeTransactions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Transform array transactions to journal collection.
|
||||||
|
this.transactionsLedger = Ledger.fromTransactions(this.transactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the G/L accounts opening balance.
|
||||||
|
*/
|
||||||
|
public async initAccountsOpeningBalance() {
|
||||||
|
// Retreive opening balance credit/debit sumation.
|
||||||
|
this.openingBalanceTransactions =
|
||||||
|
await this.repositories.transactionsRepository.journal({
|
||||||
|
toDate: moment(this.filter.fromDate).subtract(1, 'day'),
|
||||||
|
sumationCreditDebit: true,
|
||||||
|
branchesIds: this.filter.branchesIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Accounts opening transactions.
|
||||||
|
this.openingBalanceTransactionsLedger = Ledger.fromTransactions(
|
||||||
|
this.openingBalanceTransactions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the account nodes that should include transactions.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
public initAccountNodesIncludeTransactions() {
|
||||||
|
if (isEmpty(this.filter.accountsIds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const childrenNodeIds = this.filter.accountsIds?.map(
|
||||||
|
(accountId: number) => {
|
||||||
|
return this.accountsGraph.dependenciesOf(accountId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const nodeIds = R.concat(this.filter.accountsIds, childrenNodeIds);
|
||||||
|
|
||||||
|
this.accountNodesIncludeTransactions = uniq(flatten(nodeIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the account node ids should be included,
|
||||||
|
* if the filter by acounts is presented.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
public initAccountNodesIncluded() {
|
||||||
|
if (isEmpty(this.filter.accountsIds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nodeIds = this.filter.accountsIds.map((accountId) => {
|
||||||
|
const childrenIds = this.accountsGraph.dependenciesOf(accountId);
|
||||||
|
const parentIds = this.accountsGraph.dependantsOf(accountId);
|
||||||
|
|
||||||
|
return R.concat(childrenIds, parentIds);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.accountNodeInclude = R.compose(
|
||||||
|
R.uniq,
|
||||||
|
R.flatten,
|
||||||
|
R.concat(this.filter.accountsIds)
|
||||||
|
)(nodeIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,10 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { difference } from 'lodash';
|
|
||||||
import { IGeneralLedgerSheetQuery, IGeneralLedgerMeta } from '@/interfaces';
|
import { IGeneralLedgerSheetQuery, IGeneralLedgerMeta } from '@/interfaces';
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import Journal from '@/services/Accounting/JournalPoster';
|
|
||||||
import GeneralLedgerSheet from '@/services/FinancialStatements/GeneralLedger/GeneralLedger';
|
import GeneralLedgerSheet from '@/services/FinancialStatements/GeneralLedger/GeneralLedger';
|
||||||
import { transformToMap } from 'utils';
|
|
||||||
import { Tenant } from '@/system/models';
|
|
||||||
import { GeneralLedgerMeta } from './GeneralLedgerMeta';
|
import { GeneralLedgerMeta } from './GeneralLedgerMeta';
|
||||||
|
import { GeneralLedgerRepository } from './GeneralLedgerRepository';
|
||||||
const ERRORS = {
|
|
||||||
ACCOUNTS_NOT_FOUND: 'ACCOUNTS_NOT_FOUND',
|
|
||||||
};
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class GeneralLedgerService {
|
export class GeneralLedgerService {
|
||||||
@@ -40,29 +32,13 @@ export class GeneralLedgerService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates accounts existance on the storage.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number[]} accountsIds
|
|
||||||
*/
|
|
||||||
async validateAccountsExistance(tenantId: number, accountsIds: number[]) {
|
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const storedAccounts = await Account.query().whereIn('id', accountsIds);
|
|
||||||
const storedAccountsIds = storedAccounts.map((a) => a.id);
|
|
||||||
|
|
||||||
if (difference(accountsIds, storedAccountsIds).length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.ACCOUNTS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve general ledger report statement.
|
* Retrieve general ledger report statement.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IGeneralLedgerSheetQuery} query
|
* @param {IGeneralLedgerSheetQuery} query
|
||||||
* @return {IGeneralLedgerStatement}
|
* @return {Promise<IGeneralLedgerStatement>}
|
||||||
*/
|
*/
|
||||||
async generalLedger(
|
public async generalLedger(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
query: IGeneralLedgerSheetQuery
|
query: IGeneralLedgerSheetQuery
|
||||||
): Promise<{
|
): Promise<{
|
||||||
@@ -70,60 +46,24 @@ export class GeneralLedgerService {
|
|||||||
query: IGeneralLedgerSheetQuery;
|
query: IGeneralLedgerSheetQuery;
|
||||||
meta: IGeneralLedgerMeta;
|
meta: IGeneralLedgerMeta;
|
||||||
}> {
|
}> {
|
||||||
const { accountRepository, transactionsRepository, contactRepository } =
|
const repositories = this.tenancy.repositories(tenantId);
|
||||||
this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
const i18n = this.tenancy.i18n(tenantId);
|
const i18n = this.tenancy.i18n(tenantId);
|
||||||
|
|
||||||
const tenant = await Tenant.query()
|
|
||||||
.findById(tenantId)
|
|
||||||
.withGraphFetched('metadata');
|
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
...this.defaultQuery,
|
...this.defaultQuery,
|
||||||
...query,
|
...query,
|
||||||
};
|
};
|
||||||
// Retrieve all accounts with associated type from the storage.
|
const genealLedgerRepository = new GeneralLedgerRepository(
|
||||||
const accounts = await accountRepository.all();
|
repositories,
|
||||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
query,
|
||||||
|
tenantId
|
||||||
// Retrieve all contacts on the storage.
|
|
||||||
const contacts = await contactRepository.all();
|
|
||||||
const contactsByIdMap = transformToMap(contacts, 'id');
|
|
||||||
|
|
||||||
// Retreive journal transactions from/to the given date.
|
|
||||||
const transactions = await transactionsRepository.journal({
|
|
||||||
fromDate: filter.fromDate,
|
|
||||||
toDate: filter.toDate,
|
|
||||||
branchesIds: filter.branchesIds,
|
|
||||||
});
|
|
||||||
// Retreive opening balance credit/debit sumation.
|
|
||||||
const openingBalanceTrans = await transactionsRepository.journal({
|
|
||||||
toDate: moment(filter.fromDate).subtract(1, 'day'),
|
|
||||||
sumationCreditDebit: true,
|
|
||||||
branchesIds: filter.branchesIds,
|
|
||||||
});
|
|
||||||
// Transform array transactions to journal collection.
|
|
||||||
const transactionsJournal = Journal.fromTransactions(
|
|
||||||
transactions,
|
|
||||||
tenantId,
|
|
||||||
accountsGraph
|
|
||||||
);
|
|
||||||
// Accounts opening transactions.
|
|
||||||
const openingTransJournal = Journal.fromTransactions(
|
|
||||||
openingBalanceTrans,
|
|
||||||
tenantId,
|
|
||||||
accountsGraph
|
|
||||||
);
|
);
|
||||||
|
await genealLedgerRepository.asyncInitialize();
|
||||||
|
|
||||||
// General ledger report instance.
|
// General ledger report instance.
|
||||||
const generalLedgerInstance = new GeneralLedgerSheet(
|
const generalLedgerInstance = new GeneralLedgerSheet(
|
||||||
tenantId,
|
|
||||||
filter,
|
filter,
|
||||||
accounts,
|
genealLedgerRepository,
|
||||||
contactsByIdMap,
|
|
||||||
transactionsJournal,
|
|
||||||
openingTransJournal,
|
|
||||||
tenant.metadata.baseCurrency,
|
|
||||||
i18n
|
i18n
|
||||||
);
|
);
|
||||||
// Retrieve general ledger report data.
|
// Retrieve general ledger report data.
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
*/
|
*/
|
||||||
private openingBalanceColumnsAccessors(): IColumnMapperMeta[] {
|
private openingBalanceColumnsAccessors(): IColumnMapperMeta[] {
|
||||||
return [
|
return [
|
||||||
{ key: 'date', value: this.meta.fromDate },
|
{ key: 'date', value: 'Opening Balance' },
|
||||||
{ key: 'account_name', value: 'Opening Balance' },
|
{ key: 'account_name', value: '' },
|
||||||
{ key: 'reference_type', accessor: '_empty_' },
|
{ key: 'reference_type', accessor: '_empty_' },
|
||||||
{ key: 'reference_number', accessor: '_empty_' },
|
{ key: 'reference_number', accessor: '_empty_' },
|
||||||
{ key: 'description', accessor: 'description' },
|
{ key: 'description', accessor: 'description' },
|
||||||
@@ -97,12 +97,15 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Closing balance row column accessors.
|
* Closing balance row column accessors.
|
||||||
|
* @param {IGeneralLedgerSheetAccount} account -
|
||||||
* @returns {ITableColumnAccessor[]}
|
* @returns {ITableColumnAccessor[]}
|
||||||
*/
|
*/
|
||||||
private closingBalanceColumnAccessors(): IColumnMapperMeta[] {
|
private closingBalanceColumnAccessors(
|
||||||
|
account: IGeneralLedgerSheetAccount
|
||||||
|
): IColumnMapperMeta[] {
|
||||||
return [
|
return [
|
||||||
{ key: 'date', value: this.meta.toDate },
|
{ key: 'date', value: `Closing balance for ${account.name}` },
|
||||||
{ key: 'account_name', value: 'Closing Balance' },
|
{ key: 'account_name', value: `` },
|
||||||
{ key: 'reference_type', accessor: '_empty_' },
|
{ key: 'reference_type', accessor: '_empty_' },
|
||||||
{ key: 'reference_number', accessor: '_empty_' },
|
{ key: 'reference_number', accessor: '_empty_' },
|
||||||
{ key: 'description', accessor: '_empty_' },
|
{ key: 'description', accessor: '_empty_' },
|
||||||
@@ -113,6 +116,36 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closing balance row column accessors.
|
||||||
|
* @param {IGeneralLedgerSheetAccount} account -
|
||||||
|
* @returns {ITableColumnAccessor[]}
|
||||||
|
*/
|
||||||
|
private closingBalanceWithSubaccountsColumnAccessors(
|
||||||
|
account: IGeneralLedgerSheetAccount
|
||||||
|
): IColumnMapperMeta[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'date',
|
||||||
|
value: `Closing Balance for ${account.name} with sub-accounts`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'account_name',
|
||||||
|
value: ``,
|
||||||
|
},
|
||||||
|
{ key: 'reference_type', accessor: '_empty_' },
|
||||||
|
{ key: 'reference_number', accessor: '_empty_' },
|
||||||
|
{ key: 'description', accessor: '_empty_' },
|
||||||
|
{ key: 'credit', accessor: '_empty_' },
|
||||||
|
{ key: 'debit', accessor: '_empty_' },
|
||||||
|
{ key: 'amount', accessor: 'closingBalanceSubaccounts.formattedAmount' },
|
||||||
|
{
|
||||||
|
key: 'running_balance',
|
||||||
|
accessor: 'closingBalanceSubaccounts.formattedAmount',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the common table columns.
|
* Retrieves the common table columns.
|
||||||
* @returns {ITableColumn[]}
|
* @returns {ITableColumn[]}
|
||||||
@@ -184,7 +217,22 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
* @returns {ITableRow}
|
* @returns {ITableRow}
|
||||||
*/
|
*/
|
||||||
private closingBalanceMapper = (account: IGeneralLedgerSheetAccount) => {
|
private closingBalanceMapper = (account: IGeneralLedgerSheetAccount) => {
|
||||||
const columns = this.closingBalanceColumnAccessors();
|
const columns = this.closingBalanceColumnAccessors(account);
|
||||||
|
const meta = {
|
||||||
|
rowTypes: [ROW_TYPE.CLOSING_BALANCE],
|
||||||
|
};
|
||||||
|
return tableRowMapper(account, columns, meta);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the given account node to opening balance table row.
|
||||||
|
* @param {IGeneralLedgerSheetAccount} account
|
||||||
|
* @returns {ITableRow}
|
||||||
|
*/
|
||||||
|
private closingBalanceWithSubaccountsMapper = (
|
||||||
|
account: IGeneralLedgerSheetAccount
|
||||||
|
): ITableRow => {
|
||||||
|
const columns = this.closingBalanceWithSubaccountsColumnAccessors(account);
|
||||||
const meta = {
|
const meta = {
|
||||||
rowTypes: [ROW_TYPE.CLOSING_BALANCE],
|
rowTypes: [ROW_TYPE.CLOSING_BALANCE],
|
||||||
};
|
};
|
||||||
@@ -221,8 +269,27 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
rowTypes: [ROW_TYPE.ACCOUNT],
|
rowTypes: [ROW_TYPE.ACCOUNT],
|
||||||
};
|
};
|
||||||
const row = tableRowMapper(account, columns, meta);
|
const row = tableRowMapper(account, columns, meta);
|
||||||
|
const closingBalanceWithSubaccounts =
|
||||||
|
this.closingBalanceWithSubaccountsMapper(account);
|
||||||
|
|
||||||
return R.assoc('children', transactions)(row);
|
// Appends the closing balance with sub-accounts row if the account
|
||||||
|
// has children accounts and the node is define.
|
||||||
|
const isAppendClosingSubaccounts = () =>
|
||||||
|
account.children?.length > 0 && !!account.closingBalanceSubaccounts;
|
||||||
|
|
||||||
|
const children = R.compose(
|
||||||
|
R.when(
|
||||||
|
isAppendClosingSubaccounts,
|
||||||
|
R.append(closingBalanceWithSubaccounts)
|
||||||
|
),
|
||||||
|
R.concat(R.defaultTo([], transactions)),
|
||||||
|
R.when(
|
||||||
|
() => account?.children?.length > 0,
|
||||||
|
R.concat(R.defaultTo([], account.children))
|
||||||
|
)
|
||||||
|
)([]);
|
||||||
|
|
||||||
|
return R.assoc('children', children)(row);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -233,7 +300,7 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
private accountsMapper = (
|
private accountsMapper = (
|
||||||
accounts: IGeneralLedgerSheetAccount[]
|
accounts: IGeneralLedgerSheetAccount[]
|
||||||
): ITableRow[] => {
|
): ITableRow[] => {
|
||||||
return this.mapNodesDeep(accounts, this.accountMapper);
|
return this.mapNodesDeepReverse(accounts, this.accountMapper);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -250,7 +317,6 @@ export class GeneralLedgerTable extends R.compose(
|
|||||||
*/
|
*/
|
||||||
public tableColumns(): ITableColumn[] {
|
public tableColumns(): ITableColumn[] {
|
||||||
const columns = this.commonColumns();
|
const columns = this.commonColumns();
|
||||||
|
|
||||||
return R.compose(this.tableColumnsCellIndexing)(columns);
|
return R.compose(this.tableColumnsCellIndexing)(columns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Calculate the running balance.
|
||||||
|
* @param {number} amount - Transaction amount.
|
||||||
|
* @param {number} lastRunningBalance - Last running balance.
|
||||||
|
* @param {number} openingBalance - Opening balance.
|
||||||
|
* @return {number} Running balance.
|
||||||
|
*/
|
||||||
|
export function calculateRunningBalance(
|
||||||
|
amount: number,
|
||||||
|
lastRunningBalance: number
|
||||||
|
): number {
|
||||||
|
return amount + lastRunningBalance;
|
||||||
|
}
|
||||||
@@ -108,17 +108,28 @@ export default class ResourceService {
|
|||||||
const $hasFields = (field) =>
|
const $hasFields = (field) =>
|
||||||
'undefined' !== typeof field.fields ? field : undefined;
|
'undefined' !== typeof field.fields ? field : undefined;
|
||||||
|
|
||||||
const $hasColumns = (column) =>
|
const $ColumnHasColumns = (column) =>
|
||||||
'undefined' !== typeof column.columns ? column : undefined;
|
'undefined' !== typeof column.columns ? column : undefined;
|
||||||
|
|
||||||
|
const $hasColumns = (columns) =>
|
||||||
|
'undefined' !== typeof columns ? columns : undefined;
|
||||||
|
|
||||||
const naviagations = [
|
const naviagations = [
|
||||||
['fields', qim.$each, 'name'],
|
['fields', qim.$each, 'name'],
|
||||||
['fields', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
['fields', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
||||||
['fields2', qim.$each, 'name'],
|
['fields2', qim.$each, 'name'],
|
||||||
['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
||||||
['fields2', qim.$each, $hasFields, 'fields', qim.$each, 'name'],
|
['fields2', qim.$each, $hasFields, 'fields', qim.$each, 'name'],
|
||||||
['columns', qim.$each, 'name'],
|
['columns', $hasColumns, qim.$each, 'name'],
|
||||||
['columns', qim.$each, $hasColumns, 'columns', qim.$each, 'name'],
|
[
|
||||||
|
'columns',
|
||||||
|
$hasColumns,
|
||||||
|
qim.$each,
|
||||||
|
$ColumnHasColumns,
|
||||||
|
'columns',
|
||||||
|
qim.$each,
|
||||||
|
'name',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
return this.i18nService.i18nApply(naviagations, meta, tenantId);
|
return this.i18nService.i18nApply(naviagations, meta, tenantId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { FSuggest } from '../Forms';
|
||||||
|
|
||||||
|
interface BranchSuggestFieldProps {
|
||||||
|
items: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BranchSuggestField({ ...props }: BranchSuggestFieldProps) {
|
||||||
|
return (
|
||||||
|
<FSuggest
|
||||||
|
valueAccessor={'id'}
|
||||||
|
labelAccessor={'code'}
|
||||||
|
textAccessor={'name'}
|
||||||
|
inputProps={{ placeholder: 'Select a branch' }}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
import { first } from 'lodash';
|
||||||
import { DrawerHeaderContent, DrawerLoading } from '@/components';
|
import { DrawerHeaderContent, DrawerLoading } from '@/components';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
import {
|
import {
|
||||||
@@ -34,6 +35,12 @@ function CategorizeTransactionBoot({ uncategorizedTransactionId, ...props }) {
|
|||||||
isLoading: isUncategorizedTransactionLoading,
|
isLoading: isUncategorizedTransactionLoading,
|
||||||
} = useUncategorizedTransaction(uncategorizedTransactionId);
|
} = useUncategorizedTransaction(uncategorizedTransactionId);
|
||||||
|
|
||||||
|
// Retrieves the primary branch.
|
||||||
|
const primaryBranch = useMemo(
|
||||||
|
() => branches?.find((b) => b.primary) || first(branches),
|
||||||
|
[branches],
|
||||||
|
);
|
||||||
|
|
||||||
const provider = {
|
const provider = {
|
||||||
uncategorizedTransactionId,
|
uncategorizedTransactionId,
|
||||||
uncategorizedTransaction,
|
uncategorizedTransaction,
|
||||||
@@ -42,6 +49,7 @@ function CategorizeTransactionBoot({ uncategorizedTransactionId, ...props }) {
|
|||||||
accounts,
|
accounts,
|
||||||
isBranchesLoading,
|
isBranchesLoading,
|
||||||
isAccountsLoading,
|
isAccountsLoading,
|
||||||
|
primaryBranch,
|
||||||
};
|
};
|
||||||
const isLoading =
|
const isLoading =
|
||||||
isBranchesLoading || isUncategorizedTransactionLoading || isAccountsLoading;
|
isBranchesLoading || isUncategorizedTransactionLoading || isAccountsLoading;
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { FFormGroup, FeatureCan } from '@/components';
|
||||||
|
import { useCategorizeTransactionBoot } from './CategorizeTransactionBoot';
|
||||||
|
import { Features } from '@/constants';
|
||||||
|
import { BranchSuggestField } from '@/components/Branches/BranchSuggestField_';
|
||||||
|
|
||||||
|
export function CategorizeTransactionBranchField() {
|
||||||
|
const { branches } = useCategorizeTransactionBoot();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FFormGroup name={'branchId'} label={'Branch'} fastField inline>
|
||||||
|
<FeatureCan feature={Features.Branches}>
|
||||||
|
<BranchSuggestField
|
||||||
|
name={'branchId'}
|
||||||
|
items={branches}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
fill
|
||||||
|
/>
|
||||||
|
</FeatureCan>
|
||||||
|
</FFormGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -24,8 +24,11 @@ function CategorizeTransactionFormRoot({
|
|||||||
// #withDrawerActions
|
// #withDrawerActions
|
||||||
closeDrawer,
|
closeDrawer,
|
||||||
}) {
|
}) {
|
||||||
const { uncategorizedTransactionId, uncategorizedTransaction } =
|
const {
|
||||||
useCategorizeTransactionBoot();
|
uncategorizedTransactionId,
|
||||||
|
uncategorizedTransaction,
|
||||||
|
primaryBranch,
|
||||||
|
} = useCategorizeTransactionBoot();
|
||||||
const { mutateAsync: categorizeTransaction } = useCategorizeTransaction();
|
const { mutateAsync: categorizeTransaction } = useCategorizeTransaction();
|
||||||
|
|
||||||
// Callbacks handles form submit.
|
// Callbacks handles form submit.
|
||||||
@@ -43,12 +46,22 @@ function CategorizeTransactionFormRoot({
|
|||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((err) => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
if (
|
||||||
|
err.response.data?.errors?.some(
|
||||||
|
(e) => e.type === 'BRANCH_ID_REQUIRED',
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
setErrors({
|
||||||
|
branchId: 'The branch is required.',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: 'Something went wrong!',
|
message: 'Something went wrong!',
|
||||||
intent: Intent.DANGER,
|
intent: Intent.DANGER,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// Form initial values in create and edit mode.
|
// Form initial values in create and edit mode.
|
||||||
@@ -60,6 +73,9 @@ function CategorizeTransactionFormRoot({
|
|||||||
* as well.
|
* as well.
|
||||||
*/
|
*/
|
||||||
...transformToCategorizeForm(uncategorizedTransaction),
|
...transformToCategorizeForm(uncategorizedTransaction),
|
||||||
|
|
||||||
|
/** Assign the primary branch id as default value. */
|
||||||
|
branchId: primaryBranch?.id || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
|
|
||||||
export default function CategorizeTransactionOtherIncome() {
|
export default function CategorizeTransactionOtherIncome() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionOtherIncome() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<CategorizeTransactionBranchField />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
|
|
||||||
export default function CategorizeTransactionOwnerContribution() {
|
export default function CategorizeTransactionOwnerContribution() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -63,6 +64,8 @@ export default function CategorizeTransactionOwnerContribution() {
|
|||||||
<FFormGroup name={'description'} label={'Description'} fastField inline>
|
<FFormGroup name={'description'} label={'Description'} fastField inline>
|
||||||
<FTextArea name={'description'} growVertically large fill />
|
<FTextArea name={'description'} growVertically large fill />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<CategorizeTransactionBranchField />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
|
|
||||||
export default function CategorizeTransactionTransferFrom() {
|
export default function CategorizeTransactionTransferFrom() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -47,7 +48,7 @@ export default function CategorizeTransactionTransferFrom() {
|
|||||||
inline
|
inline
|
||||||
>
|
>
|
||||||
<AccountsSelect
|
<AccountsSelect
|
||||||
name={'to_account_id'}
|
name={'creditAccountId'}
|
||||||
items={accounts}
|
items={accounts}
|
||||||
filterByRootTypes={['asset']}
|
filterByRootTypes={['asset']}
|
||||||
fastField
|
fastField
|
||||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionTransferFrom() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<CategorizeTransactionBranchField />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
|
|
||||||
export default function CategorizeTransactionOtherExpense() {
|
export default function CategorizeTransactionOtherExpense() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionOtherExpense() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<CategorizeTransactionBranchField />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
|
|
||||||
export default function CategorizeTransactionOwnerDrawings() {
|
export default function CategorizeTransactionOwnerDrawings() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionOwnerDrawings() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<CategorizeTransactionBranchField />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FTextArea,
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
|
||||||
|
import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField';
|
||||||
|
|
||||||
export default function CategorizeTransactionToAccount() {
|
export default function CategorizeTransactionToAccount() {
|
||||||
const { accounts } = useCategorizeTransactionBoot();
|
const { accounts } = useCategorizeTransactionBoot();
|
||||||
@@ -49,7 +50,7 @@ export default function CategorizeTransactionToAccount() {
|
|||||||
<AccountsSelect
|
<AccountsSelect
|
||||||
name={'creditAccountId'}
|
name={'creditAccountId'}
|
||||||
items={accounts}
|
items={accounts}
|
||||||
filterByRootTypes={['assset']}
|
filterByRootTypes={['asset']}
|
||||||
fastField
|
fastField
|
||||||
fill
|
fill
|
||||||
allowCreate
|
allowCreate
|
||||||
@@ -68,6 +69,8 @@ export default function CategorizeTransactionToAccount() {
|
|||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<CategorizeTransactionBranchField />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const defaultInitialValues = {
|
|||||||
transactionType: '',
|
transactionType: '',
|
||||||
referenceNo: '',
|
referenceNo: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
branchId: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformToCategorizeForm = (uncategorizedTransaction) => {
|
export const transformToCategorizeForm = (uncategorizedTransaction) => {
|
||||||
|
|||||||
@@ -96,12 +96,19 @@ const GeneralLedgerDataTable = styled(ReportDataTable)`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:not(:first-child).is-expanded .td {
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&--OPENING_BALANCE,
|
&--OPENING_BALANCE,
|
||||||
&--CLOSING_BALANCE {
|
&--CLOSING_BALANCE {
|
||||||
|
.td {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.date {
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
.cell-inner {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
.amount {
|
.amount {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -110,6 +117,9 @@ const GeneralLedgerDataTable = styled(ReportDataTable)`
|
|||||||
.name {
|
.name {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
.td {
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -50,6 +50,9 @@ importers:
|
|||||||
'@lemonsqueezy/lemonsqueezy.js':
|
'@lemonsqueezy/lemonsqueezy.js':
|
||||||
specifier: ^2.2.0
|
specifier: ^2.2.0
|
||||||
version: 2.2.0
|
version: 2.2.0
|
||||||
|
'@supercharge/promise-pool':
|
||||||
|
specifier: ^3.2.0
|
||||||
|
version: 3.2.0
|
||||||
'@types/express':
|
'@types/express':
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
@@ -5751,6 +5754,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@supercharge/promise-pool@3.2.0:
|
||||||
|
resolution: {integrity: sha512-pj0cAALblTZBPtMltWOlZTQSLT07jIaFNeM8TWoJD1cQMgDB9mcMlVMoetiB35OzNJpqQ2b+QEtwiR9f20mADg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@surma/rollup-plugin-off-main-thread@2.2.3:
|
/@surma/rollup-plugin-off-main-thread@2.2.3:
|
||||||
resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
|
resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -17382,6 +17390,7 @@ packages:
|
|||||||
|
|
||||||
/memory-pager@1.5.0:
|
/memory-pager@1.5.0:
|
||||||
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
|
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/memorystream@0.3.1:
|
/memorystream@0.3.1:
|
||||||
@@ -23472,6 +23481,7 @@ packages:
|
|||||||
|
|
||||||
/sparse-bitfield@3.0.3:
|
/sparse-bitfield@3.0.3:
|
||||||
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
|
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
memory-pager: 1.5.0
|
memory-pager: 1.5.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|||||||
Reference in New Issue
Block a user