feat(server): wip sales tax liability summary report

This commit is contained in:
Ahmed Bouhuolia
2023-08-31 02:19:18 +02:00
parent 6535424d0f
commit 6baec8dd96
11 changed files with 535 additions and 13 deletions

View File

@@ -0,0 +1,95 @@
import { ITaxRate } from '@/interfaces';
import {
SalesTaxLiabilitySummaryQuery,
SalesTaxLiabilitySummaryRate,
SalesTaxLiabilitySummaryReportData,
SalesTaxLiabilitySummaryTotal,
} from '@/interfaces/SalesTaxLiabilitySummary';
import { sumBy } from 'lodash';
import FinancialSheet from '../FinancialSheet';
export class SalesTaxLiabilitySummary extends FinancialSheet {
query: SalesTaxLiabilitySummaryQuery;
taxRates: ITaxRate[];
payableTaxesById: any;
salesTaxesById: any;
/**
* Sales tax liability summary constructor.
* @param {SalesTaxLiabilitySummaryQuery} query
* @param {ITaxRate[]} taxRates
* @param payableTaxesById
* @param salesTaxesById
*/
constructor(
query: SalesTaxLiabilitySummaryQuery,
taxRates: ITaxRate[],
payableTaxesById: Record<
string,
{ taxRateId: number; credit: number; debit: number }
>,
salesTaxesById: Record<
string,
{ taxRateId: number; credit: number; debit: number }
>
) {
super();
this.query = query;
this.taxRates = taxRates;
this.payableTaxesById = payableTaxesById;
this.salesTaxesById = salesTaxesById;
}
/**
* Retrieves the tax rate liability node.
* @param {ITaxRate} taxRate
* @returns {SalesTaxLiabilitySummaryRate}
*/
private taxRateLiability = (
taxRate: ITaxRate
): SalesTaxLiabilitySummaryRate => {
return {
taxName: taxRate.name,
taxCode: taxRate.code,
taxableAmount: this.getAmountMeta(0),
taxAmount: this.getAmountMeta(0),
};
};
/**
* Retrieves the tax rates liability nodes.
* @returns {SalesTaxLiabilitySummaryRate[]}
*/
private taxRatesLiability = (): SalesTaxLiabilitySummaryRate[] => {
return this.taxRates.map(this.taxRateLiability);
};
/**
* Retrieves the tax rates total node.
* @param {SalesTaxLiabilitySummaryRate[]} nodes
* @returns {SalesTaxLiabilitySummaryTotal}
*/
private taxRatesTotal = (
nodes: SalesTaxLiabilitySummaryRate[]
): SalesTaxLiabilitySummaryTotal => {
const taxableAmount = sumBy(nodes, 'taxableAmount.total');
const taxAmount = sumBy(nodes, 'taxAmount.total');
return {
taxableAmount: this.getTotalAmountMeta(taxableAmount),
taxAmount: this.getTotalAmountMeta(taxAmount),
};
};
/**
* Retrieves the report data.
* @returns {SalesTaxLiabilitySummaryReportData}
*/
public reportData = (): SalesTaxLiabilitySummaryReportData => {
const taxRates = this.taxRatesLiability();
const total = this.taxRatesTotal(taxRates);
return { taxRates, total };
};
}

View File

@@ -0,0 +1,71 @@
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { keyBy } from 'lodash';
import { Inject, Service } from 'typedi';
@Service()
export class SalesTaxLiabilitySummaryRepository {
@Inject()
private tenancy: HasTenancyService;
/**
* Retrieve tax rates.
* @param tenantId
* @returns
*/
public taxRates = (tenantId: number) => {
const { TaxRate } = this.tenancy.models(tenantId);
return TaxRate.query().orderBy('name', 'desc');
};
/**
* Retrieve taxes payable sum grouped by tax rate id.
* @param {number} tenantId
* @returns
*/
public async taxesPayableSumGroupedByRateId(
tenantId: number
): Promise<
Record<string, { taxRateId: number; credit: number; debit: number }>
> {
const { AccountTransaction } = this.tenancy.models(tenantId);
const { accountRepository } = this.tenancy.repositories(tenantId);
const receivableAccount =
await accountRepository.findOrCreateAccountReceivable();
const groupedTaxesById = await AccountTransaction.query()
.where('account_id', receivableAccount.id)
.groupBy('tax_rate_id')
.select(['tax_rate_id'])
.sum('credit as credit')
.sum('debit as debit');
return keyBy(groupedTaxesById, 'taxRateId');
}
/**
* Retrieve taxes sales sum grouped by tax rate id.
* @param {number} tenantId
* @returns
*/
public taxesSalesSumGroupedByRateId = async (tenantId: number) => {
const { AccountTransaction, Account } = this.tenancy.models(tenantId);
const incomeAccounts = await Account.query().whereIn('accountType', [
ACCOUNT_TYPE.INCOME,
ACCOUNT_TYPE.OTHER_INCOME,
]);
const incomeAccountsIds = incomeAccounts.map((account) => account.id);
const groupedTaxesById = await AccountTransaction.query()
.whereIn('account_id', incomeAccountsIds)
.groupBy('tax_rate_id')
.select(['tax_rate_id'])
.sum('credit as credit')
.sum('debit as debit');
return keyBy(groupedTaxesById, 'taxRateId');
};
}

View File

@@ -0,0 +1,64 @@
import { Inject, Service } from 'typedi';
import { SalesTaxLiabilitySummaryRepository } from './SalesTaxLiabilitySummaryRepository';
import { SalesTaxLiabilitySummaryQuery } from '@/interfaces/SalesTaxLiabilitySummary';
import { SalesTaxLiabilitySummary } from './SalesTaxLiabilitySummary';
import { SalesTaxLiabilitySummaryTable } from './SalesTaxLiabilitySummaryTable';
@Service()
export class SalesTaxLiabilitySummaryService {
@Inject()
private repostiory: SalesTaxLiabilitySummaryRepository;
/**
*
* @param tenantId
* @param query
* @returns
*/
public async salesTaxLiability(
tenantId: number,
query: SalesTaxLiabilitySummaryQuery
) {
const payableByRateId =
await this.repostiory.taxesPayableSumGroupedByRateId(tenantId);
const salesByRateId = await this.repostiory.taxesSalesSumGroupedByRateId(
tenantId
);
const taxRates = await this.repostiory.taxRates(tenantId);
const taxLiabilitySummary = new SalesTaxLiabilitySummary(
query,
taxRates,
payableByRateId,
salesByRateId
);
return {
data: taxLiabilitySummary.reportData(),
query,
meta: {},
};
}
/**
*
* @param tenantId
* @param query
* @returns
*/
public async salesTaxLiabilitySummaryTable(
tenantId: number,
query: SalesTaxLiabilitySummaryQuery
) {
const report = await this.salesTaxLiability(tenantId, query);
const table = new SalesTaxLiabilitySummaryTable(report.data, query);
return {
table: {
rows: table.tableRows(),
columns: table.tableColumns(),
},
};
}
}

View File

@@ -0,0 +1,151 @@
import * as R from 'ramda';
import {
SalesTaxLiabilitySummaryQuery,
SalesTaxLiabilitySummaryRate,
SalesTaxLiabilitySummaryReportData,
SalesTaxLiabilitySummaryTotal,
} from '@/interfaces/SalesTaxLiabilitySummary';
import { tableRowMapper } from '@/utils';
import { ITableColumn, ITableColumnAccessor, ITableRow } from '@/interfaces';
enum IROW_TYPE {
TaxRate = 'TaxRate',
Total = 'Total',
}
export class SalesTaxLiabilitySummaryTable {
data: SalesTaxLiabilitySummaryReportData;
query: SalesTaxLiabilitySummaryQuery;
/**
* Sales tax liability summary table constructor.
* @param {SalesTaxLiabilitySummaryReportData} data
* @param {SalesTaxLiabilitySummaryQuery} query
*/
constructor(
data: SalesTaxLiabilitySummaryReportData,
query: SalesTaxLiabilitySummaryQuery
) {
this.data = data;
this.query = query;
}
/**
* Retrieve the tax rate row accessors.
* @returns {ITableColumnAccessor[]}
*/
private get taxRateRowAccessor() {
return [
{ key: 'taxName', value: 'taxName' },
{ key: 'taxCode', accessor: 'taxCode' },
{ key: 'taxableAmount', accessor: 'taxableAmount.formattedAmount' },
{ key: 'taxAmount', accessor: 'taxAmount.formattedAmount' },
];
}
/**
* Retrieve the tax rate total row accessors.
* @returns {ITableColumnAccessor[]}
*/
private get taxRateTotalRowAccessors() {
return [
{ key: 'taxName', value: '' },
{ key: 'taxCode', accessor: 'taxCode' },
{ key: 'taxableAmount', accessor: 'taxableAmount.formattedAmount' },
{ key: 'taxAmount', accessor: 'taxAmount.formattedAmount' },
];
}
/**
* Maps the tax rate node to table row.
* @param {SalesTaxLiabilitySummaryRate} node
* @returns {ITableRow}
*/
private taxRateTableRowMapper = (
node: SalesTaxLiabilitySummaryRate
): ITableRow => {
const columns = this.taxRateRowAccessor;
const meta = {
rowTypes: [IROW_TYPE.TaxRate],
id: node.id,
};
return tableRowMapper(node, columns, meta);
};
/**
* Maps the tax rates nodes to table rows.
* @param {SalesTaxLiabilitySummaryRate[]} nodes
* @returns {ITableRow[]}
*/
private taxRatesTableRowsMapper = (
nodes: SalesTaxLiabilitySummaryRate[]
): ITableRow[] => {
return nodes.map(this.taxRateTableRowMapper);
};
/**
* Maps the tax rate total node to table row.
* @param {SalesTaxLiabilitySummaryTotal} node
* @returns {ITableRow}
*/
private taxRateTotalRowMapper = (node: SalesTaxLiabilitySummaryTotal) => {
const columns = this.taxRateTotalRowAccessors;
const meta = {
rowTypes: [IROW_TYPE.Total],
id: node.key,
};
return tableRowMapper(node, columns, meta);
};
/**
* Retrieves the tax rate total row.
* @returns {ITableRow}
*/
private get taxRateTotalRow(): ITableRow {
return this.taxRateTotalRowMapper(this.data.total);
}
/**
* Retrieves the tax rates rows.
* @returns {ITableRow[]}
*/
private get taxRatesRows(): ITableRow[] {
return this.taxRatesTableRowsMapper(this.data.taxRates);
}
/**
* Retrieve the table rows.
* @returns {ITableRow[]}
*/
public tableRows(): ITableRow[] {
return R.compose(
R.concat(this.taxRatesRows),
R.prepend(this.taxRateTotalRow)
)([]);
}
/**
* Retrieve the table columns.
* @returns {ITableColumn[]}
*/
public tableColumns(): ITableColumn[] {
return [
{
label: 'Tax Name',
key: 'taxName',
},
{
label: 'Tax Code',
key: 'taxCode',
},
{
label: 'Taxable Amount',
key: 'taxableAmount',
},
{
label: 'Tax Rate',
key: 'taxRate',
},
];
}
}