Skip to content

Commit

Permalink
New report for avg cost of meds per patient
Browse files Browse the repository at this point in the history
  • Loading branch information
jmcameron committed Dec 7, 2021
1 parent b2a2552 commit c6016c8
Show file tree
Hide file tree
Showing 14 changed files with 395 additions and 4 deletions.
7 changes: 7 additions & 0 deletions client/src/i18n/en/report.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@
"TARGET": "Target",
"TRANSFER_PRINCIPAL": "Transfer Account to Principal Cashbox"
},
"AVG_MED_COST_PER_PATIENT" : {
"TITLE" : "Average Medication Costs Per Patient",
"DESCRIPTION" : "This report shows the average medication costs per patient over a specified time period.",
"AVG_MEDICATION_COSTS" : "Average Medication Costs",
"GRAND_TOTAL": "Grand Total",
"MEDICATION_COSTS" : "Medication Costs"
},
"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 @@ -11,6 +11,7 @@
"AGED_DEBTORS" : "Aged Debtors Report",
"AGGREGATED_STOCK_CONSUMPTION" : "Compute Aggregate Stock Consumption",
"ALLOCATION_AUX_FEES_CENTERS" : "Allocation of Auxiliary Cost Centers",
"AVERAGE_MED_COST_REPORT" : "Average Medication Costs Per Patient",
"BALANCE" : "Balance",
"BALANCE_REPORT" : "Balance of Accounts",
"BALANCE_SHEET" : "Balance Sheet",
Expand Down
7 changes: 7 additions & 0 deletions client/src/i18n/fr/report.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@
"TARGET": "Cible",
"TRANSFER_PRINCIPAL": "Compte de transfert vers caisse Principale"
},
"AVG_MED_COST_PER_PATIENT" : {
"TITLE" : "Coût moyen des médicaments par patient",
"DESCRIPTION" : "Ce rapport indique le coût moyen des médicaments par patient sur une période donnée.",
"AVG_MEDICATION_COSTS" : "Coût moyen des médicaments",
"GRAND_TOTAL": "Total général",
"MEDICATION_COSTS" : "Coûts des médicaments"
},
"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 @@ -11,6 +11,7 @@
"AGED_DEBTORS":"Balance âgée des Conventionnés",
"AGGREGATED_STOCK_CONSUMPTION" : "Consommation de stock agrégée",
"ALLOCATION_AUX_FEES_CENTERS" : "Allocation des centres de cout auxiliaires",
"AVERAGE_MED_COST_REPORT" : "Coût moyen des médicaments par patient",
"BALANCE":"Balance",
"BALANCE_REPORT":"Balance des comptes",
"BALANCE_SHEET":"Bilan",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<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 translate>REPORT.AVG_MED_COST_PER_PATIENT.TITLE</h3>
<p class="text-info" translate>REPORT.AVG_MED_COST_PER_PATIENT.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 autocomplete="off">

<!-- select depot -->
<bh-depot-select
depot-uuid="ReportConfigCtrl.reportDetails.depotUuid"
on-select-callback="ReportConfigCtrl.onSelectDepot(depot)"
required="false">
<bh-clear on-clear="ReportConfigCtrl.clearDepot()"></bh-clear>
</bh-depot-select>

<!-- service -->
<bh-service-select
service-uuid="ReportConfigCtrl.reportDetails.serviceUuid"
on-select-callback="ReportConfigCtrl.onSelectService(service)">
<bh-clear on-clear="ReportConfigCtrl.clearService()"></bh-clear>
</bh-service-select>

<!-- date interval -->
<bh-date-interval
date-from="ReportConfigCtrl.reportDetails.dateFrom"
date-to="ReportConfigCtrl.reportDetails.dateTo"
limit-min-fiscal
required="true">
</bh-date-interval>

<bh-currency-select
currency-id="ReportConfigCtrl.reportDetails.currencyId"
on-change="ReportConfigCtrl.onSelectCurrency(currency)"
required="true">
</bh-currency-select>

<!-- preview -->
<bh-loading-button loading-state="ConfigForm.$loading">
<span translate>REPORT.UTIL.PREVIEW</span>
</bh-loading-button>
</form>
</div>
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
angular.module('bhima.controllers')
.controller('avg_med_costs_per_patientController', AvgMedCostPerPatientCtrl);

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

function AvgMedCostPerPatientCtrl($sce, Notify, SavedReports, AppCache, reportData, $state, Languages, Session) {
const vm = this;
const cache = new AppCache('avg_med_costs_per_patient');
const reportUrl = 'reports/stock/avg_med_costs_per_patient';

// default values for the report parameters
vm.reportDetails = {
};

vm.previewGenerated = false;

// check cached configuration
checkCachedConfiguration();

vm.onSelectDepot = depot => {
vm.reportDetails.depotUuid = depot.uuid;
vm.reportDetails.depotName = depot.text;
};

vm.clearDepot = () => {
delete vm.reportDetails.depotUuid;
delete vm.reportDetails.depotName;
};

vm.onSelectService = service => {
vm.reportDetails.serviceUuid = service.uuid;
vm.reportDetails.serviceName = service.name;
};

vm.clearService = () => {
delete vm.reportDetails.serviceUuid;
delete vm.reportDetails.serviceName;
};

vm.onSelectCurrency = currency => {
vm.reportDetails.currencyId = currency.id;
};

vm.clear = key => {
delete vm.reportDetails[key];
};

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

vm.preview = form => {
if (form.$invalid) {
return 0;
}

// update cached configuration
cache.reportDetails = angular.copy(vm.reportDetails);
angular.extend(vm.reportDetails, { lang : Languages.key });

return SavedReports.requestPreview(reportUrl, reportData.id, angular.copy(vm.reportDetails))
.then((result) => {
vm.previewGenerated = true;
vm.previewResult = $sce.trustAsHtml(result);
})
.catch(Notify.handleError);
};

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() {
vm.reportDetails = angular.copy(cache.reportDetails || {});

// Set the defaults
if (!angular.isDefined(vm.reportDetails.currencyId)) {
vm.reportDetails.currencyId = Session.enterprise.currency_id;
}
}

}
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 @@ -11,6 +11,7 @@ angular.module('bhima.routes')
'aggregated_stock_consumption',
'analysis_auxiliary_cashboxes',
'annual_clients_report',
'avg_med_costs_per_patient',
'balance_report',
'balance_sheet_report',
'break_even',
Expand Down
1 change: 1 addition & 0 deletions server/config/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,7 @@ exports.configure = function configure(app) {
app.put('/stock/setting/:id', stockSetting.update);

// stock reports API
app.get('/reports/stock/avg_med_costs_per_patient', stockReports.stockAvgMedCostsPerPatientReport);
app.get('/reports/stock/exit', stockReports.stockExitReport);
app.get('/reports/stock/entry', stockReports.stockEntryReport);
app.get('/reports/stock/consumption_graph', stockReports.consumptionGraph);
Expand Down
2 changes: 2 additions & 0 deletions server/controllers/stock/reports/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const STOCK_ADJUSTMENT_TEMPLATE = `${BASE_PATH}/stock_adjustment.receipt.handleb
const STOCK_AGGREGATE_CONSUMPTION_TEMPLATE = `${BASE_PATH}/stock_aggregate_consumption.receipt.handlebars`;

// reports
const STOCK_AVG_MED_COSTS_PER_PATIENT_TEMPLATE = `${BASE_PATH}/stock_avg_med_costs_per_patient.report.handlebars`;
const STOCK_EXIT_REPORT_TEMPLATE = `${BASE_PATH}/stock_exit.report.handlebars`;
const STOCK_ENTRY_REPORT_TEMPLATE = `${BASE_PATH}/stock_entry.report.handlebars`;
const STOCK_LOST_STOCK_REPORT_TEMPLATE = `${BASE_PATH}/stock_lost_stock.report.handlebars`;
Expand Down Expand Up @@ -199,6 +200,7 @@ module.exports = {
STOCK_EXIT_LOSS_TEMPLATE,
POS_STOCK_EXIT_LOSS_TEMPLATE,
STOCK_ASSIGN_TEMPLATE,
STOCK_AVG_MED_COSTS_PER_PATIENT_TEMPLATE,
STOCK_ENTRY_DEPOT_TEMPLATE,
STOCK_ENTRY_PURCHASE_TEMPLATE,
STOCK_ENTRY_INTEGRATION_TEMPLATE,
Expand Down
2 changes: 2 additions & 0 deletions server/controllers/stock/reports/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const BadRequest = require('../../../lib/errors/BadRequest');
const db = require('../../../lib/db');
const Stock = require('../core');

const stockAvgMedCostsPerPatientReport = require('./stock/avg_med_costs_per_patient_report');
const stockExitReport = require('./stock/exit_report');
const stockEntryReport = require('./stock/entry_report');
const consumptionGraph = require('./stock/consumption_graph');
Expand Down Expand Up @@ -137,6 +138,7 @@ async function renderStockReceipt(req, res, next) {
exports.renderStockReceipt = renderStockReceipt;

// expose to the api
exports.stockAvgMedCostsPerPatientReport = stockAvgMedCostsPerPatientReport;
exports.stockExitReport = stockExitReport;
exports.stockEntryReport = stockEntryReport;
exports.consumptionGraph = consumptionGraph;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
const {
_, ReportManager,
STOCK_AVG_MED_COSTS_PER_PATIENT_TEMPLATE,
db,
} = require('../common');

const Exchange = require('../../../finance/exchange');

/**
* @method stockAvgMedCostsPerPatientReport
*
* @description
* Construct the report for average medication costs per patient
*
* GET /reports/stock/avg_med_costs_per_patient'
*/
async function stockAvgMedCostsPerPatientReport(req, res, next) {
let report;
const options = req.query;
const {
dateFrom, dateTo,
depotUuid, depotName,
serviceUuid, serviceName,
} = options;

const reportOptions = _.extend({}, options, {
filename : 'TREE.AVERAGE_MED_COST_REPORT',
title : 'REPORT.AVG_MED_COST_PER_PATIENT.TITLE',
});

const data = {};

const enterpriseId = req.session.enterprise.id;
const exchangeRate = await Exchange.getExchangeRate(enterpriseId, options.currencyId, new Date());
const rate = exchangeRate.rate || 1;

try {
report = new ReportManager(STOCK_AVG_MED_COSTS_PER_PATIENT_TEMPLATE, req.session, reportOptions);
} catch (e) {
return next(e);
}
const depotSql = depotUuid ? `AND sm.depot_uuid = ?` : '';
const serviceSql = serviceUuid ? `AND inv.service_uuid = ?` : '';

const sql = `
SELECT
smtot.depot_uuid, dep.text AS depot_name,
smtot.service_uuid, srv.name AS service_name,
smtot.srvTotal, smtot.srvPatCount,
(smtot.srvTotal / smtot.srvPatCount) AS srvAvgCost,
smtot.totalMedCosts, smtot.totalNumPatients,
(smtot.totalMedCosts / smtot.totalNumPatients) AS avgMedCosts
FROM (
SELECT
sm.entity_uuid AS patient_uuid,
sm.depot_uuid,
inv.service_uuid,
sm.unit_cost,
sm.quantity,
SUM(sm.quantity * sm.unit_cost) OVER (PARTITION BY sm.depot_uuid, inv.service_uuid) AS srvTotal,
-- Computes: COUNT(DISTINCT sm.entity_uuid) OVER (PARTITION BY sm.depot_uuid, inv.service_uuid)
(DENSE_RANK() OVER (PARTITION BY sm.depot_uuid,
inv.service_uuid ORDER BY sm.entity_uuid ASC) +
DENSE_RANK() OVER (PARTITION BY sm.depot_uuid,
inv.service_uuid ORDER BY sm.entity_uuid DESC) - 1) AS srvPatCount,
SUM(sm.quantity * sm.unit_cost) OVER () AS totalMedCosts,
-- Computes: COUNT(DISTINCT sm.entity_uuid) OVER (PARTITION BY sm.entity_uuid)
(DENSE_RANK() OVER (ORDER BY sm.entity_uuid ASC) +
DENSE_RANK() OVER (ORDER BY sm.entity_uuid DESC) - 1) AS totalNumPatients
FROM stock_movement AS sm
JOIN patient AS pat ON pat.uuid = sm.entity_uuid
LEFT JOIN invoice AS inv ON inv.uuid = sm.invoice_uuid
WHERE
sm.flux_id = 9 AND sm.is_exit = 1
AND DATE(sm.date) BETWEEN DATE(?) AND DATE(?)
${depotSql}
${serviceSql}
) AS smtot
JOIN depot AS dep ON dep.uuid = smtot.depot_uuid
LEFT JOIN service AS srv ON srv.uuid = smtot.service_uuid
GROUP BY smtot.depot_uuid, smtot.service_uuid
ORDER BY dep.text, srv.name
`;

const params = [dateFrom, dateTo];
if (depotUuid) {
params.push(db.bid(depotUuid));
}
if (serviceUuid) {
params.push(db.bid(serviceUuid));
}

return db.exec(sql, params)
.then((results) => {
data.currencyId = Number(options.currencyId);
data.dateFrom = dateFrom;
data.dateTo = dateTo;
data.depotName = depotName;
data.serviceName = serviceName;
data.depotOrService = depotUuid || serviceUuid;
data.depotAndService = depotUuid && serviceUuid;

results.forEach(row => {
if (row.service_name === null) {
row.service_name = 'INVENTORY.NONE';
}
row.srvTotal *= rate;
row.srvAvgCost *= rate;
});

if (results.length > 0) {
data.totalMedCosts = results[0].totalMedCosts * rate;
data.totalNumPatients = results[0].totalNumPatients;
data.avgMedCosts = results[0].avgMedCosts * rate;
}

data.rows = results;
return report.render(data);
})
.then((result) => {
res.set(result.headers).send(result.report);
})
.catch(next)
.done();
}

module.exports = stockAvgMedCostsPerPatientReport;
Loading

0 comments on commit c6016c8

Please sign in to comment.