mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
add server to monorepo.
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
import * as R from 'ramda';
|
||||
import { isEmpty, sumBy } from 'lodash';
|
||||
import {
|
||||
ITransactionsByContactsTransaction,
|
||||
ITransactionsByVendorsFilter,
|
||||
ITransactionsByVendorsTransaction,
|
||||
ITransactionsByVendorsVendor,
|
||||
ITransactionsByVendorsData,
|
||||
ILedger,
|
||||
INumberFormatQuery,
|
||||
IVendor,
|
||||
} from '@/interfaces';
|
||||
import TransactionsByContact from '../TransactionsByContact/TransactionsByContact';
|
||||
|
||||
const VENDOR_NORMAL = 'credit';
|
||||
|
||||
export default class TransactionsByVendors extends TransactionsByContact {
|
||||
readonly contacts: IVendor[];
|
||||
readonly transactionsByContact: any;
|
||||
readonly filter: ITransactionsByVendorsFilter;
|
||||
readonly baseCurrency: string;
|
||||
readonly numberFormat: INumberFormatQuery;
|
||||
readonly accountsGraph: any;
|
||||
readonly ledger: ILedger;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {IVendor} vendors
|
||||
* @param {Map<number, IAccountTransaction[]>} transactionsByContact
|
||||
* @param {string} baseCurrency
|
||||
*/
|
||||
constructor(
|
||||
vendors: IVendor[],
|
||||
accountsGraph: any,
|
||||
ledger: ILedger,
|
||||
filter: ITransactionsByVendorsFilter,
|
||||
baseCurrency: string,
|
||||
i18n
|
||||
) {
|
||||
super();
|
||||
|
||||
this.contacts = vendors;
|
||||
this.accountsGraph = accountsGraph;
|
||||
this.ledger = ledger;
|
||||
this.baseCurrency = baseCurrency;
|
||||
this.filter = filter;
|
||||
this.numberFormat = this.filter.numberFormat;
|
||||
this.i18n = i18n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the vendor transactions from the given vendor id and opening balance.
|
||||
* @param {number} vendorId - Vendor id.
|
||||
* @param {number} openingBalance - Opening balance amount.
|
||||
* @returns {ITransactionsByVendorsTransaction[]}
|
||||
*/
|
||||
private vendorTransactions(
|
||||
vendorId: number,
|
||||
openingBalance: number
|
||||
): ITransactionsByVendorsTransaction[] {
|
||||
const openingBalanceLedger = this.ledger
|
||||
.whereContactId(vendorId)
|
||||
.whereFromDate(this.filter.fromDate)
|
||||
.whereToDate(this.filter.toDate);
|
||||
|
||||
const openingEntries = openingBalanceLedger.getEntries();
|
||||
|
||||
return R.compose(
|
||||
R.curry(this.contactTransactionRunningBalance)(openingBalance, 'credit'),
|
||||
R.map(this.contactTransactionMapper.bind(this))
|
||||
).bind(this)(openingEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor section mapper.
|
||||
* @param {IVendor} vendor
|
||||
* @returns {ITransactionsByVendorsVendor}
|
||||
*/
|
||||
private vendorMapper(vendor: IVendor): ITransactionsByVendorsVendor {
|
||||
const openingBalance = this.getContactOpeningBalance(vendor.id);
|
||||
const transactions = this.vendorTransactions(vendor.id, openingBalance);
|
||||
const closingBalance = this.getVendorClosingBalance(
|
||||
transactions,
|
||||
openingBalance
|
||||
);
|
||||
const currencyCode = this.baseCurrency;
|
||||
|
||||
return {
|
||||
vendorName: vendor.displayName,
|
||||
openingBalance: this.getTotalAmountMeta(openingBalance, currencyCode),
|
||||
closingBalance: this.getTotalAmountMeta(closingBalance, currencyCode),
|
||||
transactions,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the vendor closing balance from the given customer transactions.
|
||||
* @param {ITransactionsByContactsTransaction[]} customerTransactions
|
||||
* @param {number} openingBalance
|
||||
* @returns
|
||||
*/
|
||||
private getVendorClosingBalance(
|
||||
customerTransactions: ITransactionsByContactsTransaction[],
|
||||
openingBalance: number
|
||||
) {
|
||||
return this.getContactClosingBalance(
|
||||
customerTransactions,
|
||||
VENDOR_NORMAL,
|
||||
openingBalance
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the vendors post filter is active.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private isVendorsPostFilter = (): boolean => {
|
||||
return isEmpty(this.filter.vendorsIds);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the vendors sections of the report.
|
||||
* @param {IVendor[]} vendors
|
||||
* @returns {ITransactionsByVendorsVendor[]}
|
||||
*/
|
||||
private vendorsMapper(vendors: IVendor[]): ITransactionsByVendorsVendor[] {
|
||||
return R.compose(
|
||||
R.when(this.isVendorsPostFilter, this.contactsFilter),
|
||||
R.map(this.vendorMapper.bind(this))
|
||||
).bind(this)(vendors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the report data.
|
||||
* @returns {ITransactionsByVendorsData}
|
||||
*/
|
||||
public reportData(): ITransactionsByVendorsData {
|
||||
return this.vendorsMapper(this.contacts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the report columns.
|
||||
*/
|
||||
public reportColumns() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { isEmpty, map } from 'lodash';
|
||||
import { IVendor, IAccount, IAccountTransaction } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||
|
||||
@Service()
|
||||
export default class TransactionsByVendorRepository {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve the report vendors.
|
||||
* @param {number} tenantId
|
||||
* @returns {Promise<IVendor[]>}
|
||||
*/
|
||||
public getVendors(
|
||||
tenantId: number,
|
||||
vendorsIds?: number[]
|
||||
): Promise<IVendor[]> {
|
||||
const { Vendor } = this.tenancy.models(tenantId);
|
||||
|
||||
return Vendor.query().onBuild((q) => {
|
||||
q.orderBy('displayName');
|
||||
|
||||
if (!isEmpty(vendorsIds)) {
|
||||
q.whereIn('id', vendorsIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the accounts receivable.
|
||||
* @param {number} tenantId
|
||||
* @returns {Promise<IAccount[]>}
|
||||
*/
|
||||
private async getPayableAccounts(tenantId: number): Promise<IAccount[]> {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
const accounts = await Account.query().where(
|
||||
'accountType',
|
||||
ACCOUNT_TYPE.ACCOUNTS_PAYABLE
|
||||
);
|
||||
return accounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the customers opening balance transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} openingDate
|
||||
* @param {number} customersIds
|
||||
* @returns {}
|
||||
*/
|
||||
public async getVendorsOpeningBalance(
|
||||
tenantId: number,
|
||||
openingDate: Date,
|
||||
customersIds?: number[]
|
||||
): Promise<IAccountTransaction[]> {
|
||||
const { AccountTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
const payableAccounts = await this.getPayableAccounts(tenantId);
|
||||
const payableAccountsIds = map(payableAccounts, 'id');
|
||||
|
||||
const openingTransactions = await AccountTransaction.query().modify(
|
||||
'contactsOpeningBalance',
|
||||
openingDate,
|
||||
payableAccountsIds,
|
||||
customersIds
|
||||
);
|
||||
return openingTransactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve vendors periods transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {Date|string} openingDate
|
||||
* @param {number[]} customersIds
|
||||
*/
|
||||
public async getVendorsPeriodTransactions(
|
||||
tenantId: number,
|
||||
fromDate: Date,
|
||||
toDate: Date
|
||||
): Promise<IAccountTransaction[]> {
|
||||
const { AccountTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
const receivableAccounts = await this.getPayableAccounts(tenantId);
|
||||
const receivableAccountsIds = map(receivableAccounts, 'id');
|
||||
|
||||
const transactions = await AccountTransaction.query().onBuild((query) => {
|
||||
// Filter by date.
|
||||
query.modify('filterDateRange', fromDate, toDate);
|
||||
|
||||
// Filter by customers.
|
||||
query.whereNot('contactId', null);
|
||||
|
||||
// Filter by accounts.
|
||||
query.whereIn('accountId', receivableAccountsIds);
|
||||
});
|
||||
return transactions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import { Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import { map } from 'lodash';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import {
|
||||
ITransactionsByVendorsService,
|
||||
ITransactionsByVendorsFilter,
|
||||
ITransactionsByVendorsStatement,
|
||||
ILedgerEntry,
|
||||
} from '@/interfaces';
|
||||
import TransactionsByVendor from './TransactionsByVendor';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
import TransactionsByVendorRepository from './TransactionsByVendorRepository';
|
||||
import { Tenant } from '@/system/models';
|
||||
|
||||
export default class TransactionsByVendorsService
|
||||
implements ITransactionsByVendorsService
|
||||
{
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
@Inject()
|
||||
reportRepository: TransactionsByVendorRepository;
|
||||
|
||||
/**
|
||||
* Defaults balance sheet filter query.
|
||||
* @return {IVendorBalanceSummaryQuery}
|
||||
*/
|
||||
get defaultQuery(): ITransactionsByVendorsFilter {
|
||||
return {
|
||||
fromDate: moment().format('YYYY-MM-DD'),
|
||||
toDate: moment().format('YYYY-MM-DD'),
|
||||
numberFormat: {
|
||||
precision: 2,
|
||||
divideOn1000: false,
|
||||
showZero: false,
|
||||
formatMoney: 'total',
|
||||
negativeFormat: 'mines',
|
||||
},
|
||||
comparison: {
|
||||
percentageOfColumn: true,
|
||||
},
|
||||
noneZero: false,
|
||||
noneTransactions: true,
|
||||
vendorsIds: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the customers opening balance transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} openingDate
|
||||
* @param {number} customersIds
|
||||
* @returns {Promise<ILedgerEntry[]>}
|
||||
*/
|
||||
private async getVendorsOpeningBalanceEntries(
|
||||
tenantId: number,
|
||||
openingDate: Date,
|
||||
customersIds?: number[]
|
||||
): Promise<ILedgerEntry[]> {
|
||||
const openingTransactions =
|
||||
await this.reportRepository.getVendorsOpeningBalance(
|
||||
tenantId,
|
||||
openingDate,
|
||||
customersIds
|
||||
);
|
||||
return R.compose(
|
||||
R.map(R.assoc('date', openingDate)),
|
||||
R.map(R.assoc('accountNormal', 'credit'))
|
||||
)(openingTransactions);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {Date|string} openingDate
|
||||
* @param {number[]} customersIds
|
||||
*/
|
||||
private async getVendorsPeriodEntries(
|
||||
tenantId: number,
|
||||
fromDate: Date,
|
||||
toDate: Date
|
||||
): Promise<ILedgerEntry[]> {
|
||||
const transactions =
|
||||
await this.reportRepository.getVendorsPeriodTransactions(
|
||||
tenantId,
|
||||
fromDate,
|
||||
toDate
|
||||
);
|
||||
return R.compose(
|
||||
R.map(R.assoc('accountNormal', 'credit')),
|
||||
R.map((trans) => ({
|
||||
...trans,
|
||||
referenceTypeFormatted: trans.referenceTypeFormatted,
|
||||
}))
|
||||
)(transactions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the report ledger entries from repository.
|
||||
* @param {number} tenantId
|
||||
* @param {Date} fromDate
|
||||
* @param {Date} toDate
|
||||
* @returns {Promise<ILedgerEntry[]>}
|
||||
*/
|
||||
private async getReportEntries(
|
||||
tenantId: number,
|
||||
fromDate: Date,
|
||||
toDate: Date
|
||||
): Promise<ILedgerEntry[]> {
|
||||
const openingBalanceDate = moment(fromDate).subtract(1, 'days').toDate();
|
||||
|
||||
return [
|
||||
...(await this.getVendorsOpeningBalanceEntries(
|
||||
tenantId,
|
||||
openingBalanceDate
|
||||
)),
|
||||
...(await this.getVendorsPeriodEntries(tenantId, fromDate, toDate)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve transactions by by the customers.
|
||||
* @param {number} tenantId
|
||||
* @param {ITransactionsByVendorsFilter} query
|
||||
* @return {Promise<ITransactionsByVendorsStatement>}
|
||||
*/
|
||||
public async transactionsByVendors(
|
||||
tenantId: number,
|
||||
query: ITransactionsByVendorsFilter
|
||||
): Promise<ITransactionsByVendorsStatement> {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const i18n = this.tenancy.i18n(tenantId);
|
||||
|
||||
const tenant = await Tenant.query()
|
||||
.findById(tenantId)
|
||||
.withGraphFetched('metadata');
|
||||
|
||||
const filter = { ...this.defaultQuery, ...query };
|
||||
|
||||
// Retrieve the report vendors.
|
||||
const vendors = await this.reportRepository.getVendors(
|
||||
tenantId,
|
||||
filter.vendorsIds
|
||||
);
|
||||
// Retrieve the accounts graph.
|
||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||
|
||||
// Journal transactions.
|
||||
const reportEntries = await this.getReportEntries(
|
||||
tenantId,
|
||||
filter.fromDate,
|
||||
filter.toDate
|
||||
);
|
||||
// Ledger collection.
|
||||
const journal = new Ledger(reportEntries);
|
||||
|
||||
// Transactions by customers data mapper.
|
||||
const reportInstance = new TransactionsByVendor(
|
||||
vendors,
|
||||
accountsGraph,
|
||||
journal,
|
||||
filter,
|
||||
tenant.metadata.baseCurrency,
|
||||
i18n
|
||||
);
|
||||
return {
|
||||
data: reportInstance.reportData(),
|
||||
columns: reportInstance.reportColumns(),
|
||||
query: filter,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import * as R from 'ramda';
|
||||
import { tableRowMapper } from 'utils';
|
||||
import { ITransactionsByVendorsVendor, ITableRow } from '@/interfaces';
|
||||
import TransactionsByContactsTableRows from '../TransactionsByContact/TransactionsByContactTableRows';
|
||||
|
||||
enum ROW_TYPE {
|
||||
OPENING_BALANCE = 'OPENING_BALANCE',
|
||||
CLOSING_BALANCE = 'CLOSING_BALANCE',
|
||||
TRANSACTION = 'TRANSACTION',
|
||||
VENDOR = 'VENDOR',
|
||||
}
|
||||
|
||||
export default class TransactionsByVendorsTableRows extends TransactionsByContactsTableRows {
|
||||
vendorsTransactions: ITransactionsByVendorsVendor[];
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
*/
|
||||
constructor(
|
||||
vendorsTransactions: ITransactionsByVendorsVendor[],
|
||||
i18n
|
||||
) {
|
||||
super();
|
||||
|
||||
this.vendorsTransactions = vendorsTransactions;
|
||||
this.i18n = i18n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the table row of vendor details.
|
||||
* @param {ITransactionsByVendorsVendor} vendor -
|
||||
* @returns {ITableRow[]}
|
||||
*/
|
||||
private vendorDetails = (vendor: ITransactionsByVendorsVendor) => {
|
||||
const columns = [
|
||||
{ key: 'vendorName', accessor: 'vendorName' },
|
||||
...R.repeat({ key: 'empty', value: '' }, 5),
|
||||
{
|
||||
key: 'closingBalanceValue',
|
||||
accessor: 'closingBalance.formattedAmount',
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
...tableRowMapper(vendor, columns, { rowTypes: [ROW_TYPE.VENDOR] }),
|
||||
children: R.pipe(
|
||||
R.when(
|
||||
R.always(vendor.transactions.length > 0),
|
||||
R.pipe(
|
||||
R.concat(this.contactTransactions(vendor)),
|
||||
R.prepend(this.contactOpeningBalance(vendor))
|
||||
)
|
||||
),
|
||||
R.append(this.contactClosingBalance(vendor))
|
||||
)([]),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the table rows of the vendor section.
|
||||
* @param {ITransactionsByVendorsVendor} vendor
|
||||
* @returns {ITableRow[]}
|
||||
*/
|
||||
private vendorRowsMapper = (vendor: ITransactionsByVendorsVendor) => {
|
||||
return R.pipe(this.vendorDetails)(vendor);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the table rows of transactions by vendors report.
|
||||
* @param {ITransactionsByVendorsVendor[]} vendors
|
||||
* @returns {ITableRow[]}
|
||||
*/
|
||||
public tableRows = (): ITableRow[] => {
|
||||
return R.map(this.vendorRowsMapper)(this.vendorsTransactions);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user