Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(Budget Report) #7684

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions client/src/i18n/en/report.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@
"GRAND_TOTAL": "Grand Total",
"MEDICATION_COSTS" : "Medication Costs"
},
"BUDGET_REPORT":{
"TITLE" : "Budget Report",
"COMPLETION_RATE" : "Completion rate",
"DEFAULT_SETTING": "Default Setting",
"DESCRIPTION" : "The Budget Management Report is a comprehensive tool designed to provide detailed insights into the financial planning and allocation processes.",
"DISPLAY_ALL_ACCOUNTS" : "Display all accounts",
"EXPENSES": "Expenses",
"HIDE_TITLE_ACCOUNT": "Hide Title Account",
"HIDE_UNUSED_ACCOUNTS": "Hide Unused Accounts or Accounts with Zero Values",
"MAX_5_YEAR": "Please note that the budget analysis period is limited to a maximum of 5 years.",
"PERCENTAGE_VARIATION_COMPARED": "Percentage Variation Compared to the Budget",
"REALIZATION" : "Actuals",
"REVENUS": "Income",
"SET_NUMBER_YEAR": "Set the Number of Years for Analysis",
"SHOW_ONLY_TITLE_ACCOUNT": "Display Only the Title Account",
"VARIATION_IN_AMOUNT": "Variation in Amount"
},
"BY_ASC": "By Ascending Order",
"BY_DESC": "By Descending Order",
"BALANCE": "Balance Report",
Expand Down
1 change: 1 addition & 0 deletions client/src/i18n/en/tree.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"BREAK_EVEN_REFERENCE":"Break-even references",
"BREAK_EVEN_REPORT": "Break-even Report",
"BUDGET": "Budget Management",
"BUDGET_REPORT": "Budget Report",
"CASHBOX_MANAGEMENT" : "Cashbox Management",
"CASHFLOW_BY_SERVICE" : "Cashflow by Service",
"CASHFLOW" : "Statement of Cash Flows",
Expand Down
4 changes: 2 additions & 2 deletions client/src/i18n/fr/budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"BUDGET_YTD_COLUMN": "Budget",
"BUDGET_YTD_SUBTITLE": "(CDA)",
"BUDGET_YTD_TOOLTIP": "Budget cumul de l'année (CDA)",
"EXPENSES_TOTAL": "Dépenses totales",
"EXPENSES_TOTAL": "Total des Dépenses",
"DEVIATION_YTD": "Déviation % (CDA)",
"DEVIATION_YTD_TOOLTIP": "Déviation % (CDA) = 100*(Réels_CDA/budget_CDA)",
"DIFFERENCE_YTD": "Différence (CDA)",
Expand All @@ -26,7 +26,7 @@
"IMPORT_BUDGET_ERROR_LOCKED_ACCOUNT": "ERREUR : L'importation du budget a échoué ! Les données d'importation du budget comprennent un compte bloqué. Veuillez le supprimer et réessayer :",
"IMPORT_BUDGET_ERROR_NEGATIVE_BUDGET_VALUE": "ERREUR : L'importation du budget a échoué ! La valeur du budget est négative.",
"IMPORT_BUDGET_WARN_TITLE_BUDGET_IGNORED": "ATTENTION : Les valeurs budgétaires des comptes titres sont ignorées.",
"INCOME_TOTAL": "Revenu totales",
"INCOME_TOTAL": "Total des Revenus",
"TOTAL_SUMMARY": "Résumé des totaux (réel - budget)",
"ACTUALS": {
"JANUARY": "Réalisations de janvier",
Expand Down
17 changes: 17 additions & 0 deletions client/src/i18n/fr/report.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@
"GRAND_TOTAL": "Total général",
"MEDICATION_COSTS" : "Coûts des médicaments"
},
"BUDGET_REPORT":{
"TITLE" : "Rapport Budgetaire",
"COMPLETION_RATE" : "Taux de réalisation",
"DEFAULT_SETTING": "Paramètre par défaut",
"DESCRIPTION" : "Le rapport de gestion budgétaire est un outil complet conçu pour fournir des informations détaillées sur les processus de planification et d'allocation financières.",
"DISPLAY_ALL_ACCOUNTS" : "Afficher tous les comptes",
"EXPENSES": "Dépenses",
"HIDE_TITLE_ACCOUNT": "Cacher le compte de titre",
"HIDE_UNUSED_ACCOUNTS": "Cacher le compte non utilisé ou dont les valeurs sont égales à zéro",
"MAX_5_YEAR": "Veuillez noter que la période d'analyse budgétaire est limitée à un maximum de 5 années.",
"PERCENTAGE_VARIATION_COMPARED": "Variation en pourcentage par rapport au budget",
"REALIZATION" : "Réalisation",
"REVENUS": "Revenus",
"SET_NUMBER_YEAR": "Définir le Nombre d'années pour l'Analyse",
"SHOW_ONLY_TITLE_ACCOUNT": "Afficher uniquement le compte de titre",
"VARIATION_IN_AMOUNT": "Variation en chiffre"
},
"BY_ASC": "Par ordre croissant",
"BY_DESC": "Par ordre décroissant",
"BALANCE": "Rapport de la Balance",
Expand Down
1 change: 1 addition & 0 deletions client/src/i18n/fr/tree.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"BREAK_EVEN_REFERENCE":"Références du seuil de rentabilité",
"BREAK_EVEN_REPORT": "Rapport du seuil de rentabilité",
"BUDGET": "Gestion budgétaire",
"BUDGET_REPORT": "Rapport Budgétaire",
"CASHBOX_MANAGEMENT":"Caisses et Banques",
"CASHFLOW": "Flux de Trésorerie",
"CASHFLOW_BY_SERVICE": "Journal de Ventilation",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
angular.module('bhima.controllers')
.controller('budget_reportController', BudgetReportController);

BudgetReportController.$inject = [
'$sce', 'NotifyService', 'BaseReportService', 'AppCache', 'reportData', '$state', 'SessionService',
];

function BudgetReportController($sce, Notify, SavedReports, AppCache, reportData, $state, Session) {
const vm = this;
const cache = new AppCache('configure_budget_report');
const reportUrl = 'reports/finance/budget_report';

vm.reportDetails = {
currency_id : Session.enterprise.currency_id,
set_number_year : 1,
filter : 'default',
};

vm.previewGenerated = false;
checkCachedConfiguration();

vm.onSelectFiscalYear = (fiscalYear) => {
vm.reportDetails.fiscal_id = fiscalYear.id;
};

vm.onSelectCurrency = (currency) => {
vm.reportDetails.currency_id = currency.id;
};

vm.onSelectCronReport = report => {
vm.reportDetails = angular.copy(report);
};

vm.numberYears = [
{ id : 1 }, { id : 2 }, { id : 3 }, { id : 4 }, { id : 5 },
];

vm.preview = function preview(form) {
if (form.$invalid) { return null; }

// update cached configuration
cache.reportDetails = angular.copy(vm.reportDetails);

return SavedReports.requestPreview(reportUrl, reportData.id, angular.copy(vm.reportDetails))
jmcameron marked this conversation as resolved.
Show resolved Hide resolved
.then((result) => {
vm.previewGenerated = true;
vm.previewResult = $sce.trustAsHtml(result);
})
.catch(Notify.handleError);
};

vm.clearPreview = function clearPreview() {
vm.previewGenerated = false;
vm.previewResult = null;
};

vm.clear = (value) => {
delete vm.reportDetails[value];
};

vm.filterBudget = [
{ value : 'default', label : 'REPORT.BUDGET_REPORT.DISPLAY_ALL_ACCOUNTS' },
{ value : 'hide_title', label : 'REPORT.BUDGET_REPORT.HIDE_TITLE_ACCOUNT' },
{ value : 'show_title', label : 'REPORT.BUDGET_REPORT.SHOW_ONLY_TITLE_ACCOUNT' },
];

vm.requestSaveAs = function requestSaveAs() {

const options = {
url : reportUrl,
report : reportData,
reportOptions : angular.copy(vm.reportDetails),
};

return SavedReports.saveAsModal(options)
.then(() => {
$state.go('reportsBase.reportsArchive', { key : options.report.report_key });
})
.catch(Notify.handleError);
};

function checkCachedConfiguration() {
if (cache.reportDetails) {
vm.reportDetails = angular.copy(cache.reportDetails);
}
vm.reportDetails.type = 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<bh-report-preview
ng-if="ReportConfigCtrl.previewGenerated"
source-document="ReportConfigCtrl.previewResult"
on-clear-callback="ReportConfigCtrl.clearPreview()"
on-save-callback="ReportConfigCtrl.requestSaveAs()">
</bh-report-preview>

<div ng-show="!ReportConfigCtrl.previewGenerated">
<div class="row">
<div class="col-md-12">
<h3 class="text-capitalize" translate>REPORT.BUDGET_REPORT.TITLE</h3>
<p class="text-info" translate>REPORT.BUDGET_REPORT.DESCRIPTION</p>
</div>
</div>

<div class="row" style="margin-top : 10px">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<span translate>REPORT.UTIL.OPTIONS</span>
</div>

<div class="panel-body">
<form name="ConfigForm" bh-submit="ReportConfigCtrl.preview(ConfigForm)" novalidate>
<bh-fiscal-year-select
fiscal-id="ReportConfigCtrl.reportDetails.fiscal_id"
on-select-fiscal-callback="ReportConfigCtrl.onSelectFiscalYear(fiscalYear)"
required="true">
</bh-fiscal-year-select>

<div class="form-group" ng-class="{'has-error' : ConfigForm.set_number_year.$invalid && ConfigForm.$submitted}">
<label class="control-label" translate>REPORT.BUDGET_REPORT.SET_NUMBER_YEAR</label>
<bh-clear on-clear="ReportConfigCtrl.clear('id')"></bh-clear>
<ui-select
name="set_number_year"
ng-model="ReportConfigCtrl.reportDetails.set_number_year">

<ui-select-match placeholder="{{ 'REPORT.BUDGET_REPORT.MAX_5_YEAR' | translate }}">
<span>{{$select.selected.id}}</span>
</ui-select-match>

<ui-select-choices repeat="year.id as year in ReportConfigCtrl.numberYears | filter:{id: $select.search}">
<strong ng-bind-html="year.id | highlight:$select.search"></strong>
</ui-select-choices>
</ui-select>
<div class="help-block" ng-messages="ConfigForm.id.$error" ng-show="ConfigForm.$submitted">
<div ng-messages-include="modules/templates/messages.tmpl.html"></div>
</div>
</div>

<div class="form-group" ng-class="{ 'has-error' : ConfigForm.$submitted && ConfigForm.budgetFilter.$invalid }">
<div ng-repeat="budgetFilter in ReportConfigCtrl.filterBudget" class="radio">
<label>
<input
name="filter"
type="radio"
ng-model="ReportConfigCtrl.reportDetails.filter"
ng-value="budgetFilter.value"
data-report-format-option="{{ budgetFilter.value }}"
required>
<span translate>{{budgetFilter.label}}</span>
</label>
</div>

<div class="help-block" ng-messages="ConfigForm.filter.$error" ng-show="ConfigForm.$submitted">
<div ng-messages-include="modules/templates/messages.tmpl.html"></div>
</div>
</div>

<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="ReportConfigCtrl.reportDetails.hide_unused" ng-true-value="1" ng-false-value="0">
<span translate>REPORT.BUDGET_REPORT.HIDE_UNUSED_ACCOUNTS</span>
</label>
</div>
</div>

<bh-loading-button loading-state="ConfigForm.$loading">
<span translate>REPORT.UTIL.PREVIEW</span>
</bh-loading-button>
</form>
</div>
</div>
</div>

<div class="col-md-6">
<bh-cron-email-report
report-key="operating"
report-form="ConfigForm"
report-details="ReportConfigCtrl.reportDetails"
on-select-report="ReportConfigCtrl.onSelectCronReport(report)">
</bh-cron-email-report>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions client/src/modules/reports/reports.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ angular.module('bhima.routes')
'system_usage_stat',
'unpaid_invoice_payments',
'visit_report',
'budget_report',
];

function resolveReportData($stateParams, SavedReports) {
Expand Down
1 change: 1 addition & 0 deletions server/config/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ exports.configure = function configure(app) {

app.get('/reports/finance/analysis_auxiliary_cashboxes', financeReports.analysisAuxiliaryCashboxes.report);
app.get('/reports/finance/configurable_analysis_report', financeReports.configurableAnalysisReport.report);
app.get('/reports/finance/budget_report', financeReports.budget_analytical.report);

// visits reports
app.get('/reports/visits', medicalReports.visitsReports.document);
Expand Down
30 changes: 22 additions & 8 deletions server/controllers/finance/budget/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,13 @@ function buildBudgetData(fiscalYearId) {
let accounts;
let periodActuals;

// Get basic info on all relevant accounts
// Get basic info on all relevant accounts excluding hidden and blocked accounts.
const accountsSql = `
SELECT
a.id, a.number, a.label, a.locked, a.type_id,
a.parent, a.locked, a.hidden
FROM account AS a
WHERE a.type_id in (${allowedTypes}) AND a.locked = 0;
WHERE a.type_id in (${allowedTypes}) AND a.locked = 0 AND a.hidden = 0;
`;

// First get the basic account and FY budget data (if available)
Expand All @@ -105,9 +105,10 @@ function buildBudgetData(fiscalYearId) {
JOIN period AS p ON p.id = b.period_id
WHERE p.number = 0 and p.fiscal_year_id = ?
) AS bdata ON bdata.account_id = a.id
WHERE a.type_id in (${INCOME}, ${EXPENSE}) AND a.locked = 0;
WHERE a.type_id in (${INCOME}, ${EXPENSE}) AND a.locked = 0 AND a.hidden = 0;
`;

// Retrieving the values for the periods, excluding period 0 and the closing period 13
const actualsSql = `
SELECT
a.id,
Expand All @@ -116,17 +117,21 @@ function buildBudgetData(fiscalYearId) {
FROM period_total pt
JOIN account AS a ON a.id = pt.account_id
JOIN account_type AS at ON at.id = a.type_id
JOIN period AS p ON p.id = pt.period_id
WHERE pt.fiscal_year_id = ? AND a.type_id in (${INCOME}, ${EXPENSE}) AND a.locked = 0
AND a.hidden = 0 AND (p.number <> 0 AND p.number <> 13)
GROUP BY a.id;
`;

// Retrieving the values for the periods, excluding period 0 and the closing period 13
const periodActualsSql = `
SELECT a.id, pt.debit, pt.credit, p.number AS periodNum
FROM period_total pt
JOIN period AS p ON p.id = pt.period_id
JOIN account AS a ON a.id = pt.account_id
JOIN account_type AS at ON at.id = a.type_id
WHERE pt.fiscal_year_id = ? AND a.type_id in (${INCOME}, ${EXPENSE}) AND a.locked = 0;
WHERE pt.fiscal_year_id = ? AND a.type_id in (${INCOME}, ${EXPENSE}) AND a.locked = 0
AND a.hidden = 0 AND (p.number <> 0 AND p.number <> 13);
`;

const months = constants.periods.filter(elt => elt.periodNum !== 0);
Expand Down Expand Up @@ -269,8 +274,17 @@ function getBudgetData(req, res, next) {
function sortAccounts(origAccounts, allAccounts) {

// first separate the types of accounts
const expenses = origAccounts.filter(item => item.type_id === EXPENSE).sort((a, b) => a.number - b.number);
const incomes = origAccounts.filter(item => item.type_id === INCOME).sort((a, b) => a.number - b.number);
const expenses = origAccounts.filter(item => item.type_id === EXPENSE);
const incomes = origAccounts.filter(item => item.type_id === INCOME);

// Improvement of the function for sorting account numbers by treating account numbers as strings.
expenses.sort((a, b) => {
return String(a.number).localeCompare(String(b.number));
});

incomes.sort((a, b) => {
return String(a.number).localeCompare(String(b.number));
});

// Construct the list of periods (leave out the FY total period)
const periods = constants.periods.filter(elt => elt.periodNum !== 0);
Expand Down Expand Up @@ -492,7 +506,7 @@ function computeTitleAccountTotals(budgetAccts, allAccounts) {
const childrenIDs = getChildrenAccounts(allAccounts, acct.id);
childrenIDs.forEach(childId => {
const bdAcct = budgetAccts.find(item => item.id === childId);
if (bdAcct && bdAcct.budget) {
if (bdAcct) {
if (bdAcct.type_id === INCOME) {
acct.isIncomeTitle = true;
} else if (bdAcct.type_id === EXPENSE) {
Expand Down Expand Up @@ -701,7 +715,7 @@ function computeTitleAccountPeriodTotals(budgetAccts, allAccounts) {
const childrenIDs = getChildrenAccounts(allAccounts, acct.id);
childrenIDs.forEach(childId => {
const bdAcct = budgetAccts.find(item => item.id === childId);
if (bdAcct && bdAcct.budget) {
if (bdAcct) {
if ((bdAcct.type_id === INCOME) || (bdAcct.type_id === EXPENSE)) {
acct.actuals += bdAcct.actuals ? bdAcct.actuals : 0;
acct.credit += bdAcct.credit ? bdAcct.credit : 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<!-- data -->
<table class="table table-condensed table-bordered table-report">
<thead>
<tr style="background-color:#ddd;">
<tr style="background-color:#A0A0A0;">
<th class="text-left">{{translate 'TABLE.COLUMNS.ACCOUNT'}}</th>
<th class="text-left">{{translate 'TABLE.COLUMNS.LABEL'}}</th>
<th class="text-left">{{translate 'TABLE.COLUMNS.TYPE'}}</th>
Expand Down
Loading