diff --git a/client/src/i18n/en/report.json b/client/src/i18n/en/report.json index 09bd2a5bc0..6a03e8f645 100644 --- a/client/src/i18n/en/report.json +++ b/client/src/i18n/en/report.json @@ -143,6 +143,14 @@ "DESCRIPTION": "This report shows the difference between what we purchase with what we have entered in the stock.", "TITLE": "Purchase Order Analysis" }, + "RUMER": { + "DESCRIPTION": "Drug Usage and Recipe Register", + "INCLUDE_OUT_STOCK_ITEMS": "Include out-of-stock items", + "STOCK_BEGINNING": "Stock at the beginning of the month", + "STOCK_END": "Stock at the end of the month", + "TITLE": "RUMER", + "TOTAL_ENTRIES": "Total entries for the month" + }, "STOCK_VALUE": { "TITLE": "Stock value report", "DESCRIPTION": "This report shows the current stock value for each inventory in a depot", diff --git a/client/src/i18n/en/tree.json b/client/src/i18n/en/tree.json index f37e50bff1..a523fd8c3c 100644 --- a/client/src/i18n/en/tree.json +++ b/client/src/i18n/en/tree.json @@ -139,6 +139,7 @@ "ROLE_MANAGEMENT" : "Role Management", "ROOT" : "Root", "RUBRIC_MANAGEMENT" : "Rubrics Management", + "RUMER_REPORT" : "Rumer", "SERVICE" : "Services", "SIMPLE_VOUCHER" : "Simple Voucher", "STAFFING_INDICES_MANAGEMENT" : "Staffing indices management", diff --git a/client/src/i18n/fr/report.json b/client/src/i18n/fr/report.json index faa45147da..916c3d7248 100644 --- a/client/src/i18n/fr/report.json +++ b/client/src/i18n/fr/report.json @@ -132,6 +132,14 @@ "TITLE": "Situation financière d'un Patient", "TOTAL_ALL_STOCK_MOV": "Total de tous les mouvements de stocks" }, + "RUMER": { + "DESCRIPTION": "Registre d’utilisation des médicaments et recettes", + "INCLUDE_OUT_STOCK_ITEMS": "Inclure les articles en rupture de stock", + "STOCK_BEGINNING": "Stock au début du mois", + "STOCK_END": "Stock à la fin du mois", + "TITLE": "RUMER", + "TOTAL_ENTRIES": "Total des entrées du mois" + }, "PURCHASE_ORDER_ANALYSIS": { "DESCRIPTION": "Ce rapport permet de connaitre la différence entre ce la quantité commandée et la quantité entrée en stock", "TITLE": "Analyse de commande d'achat" diff --git a/client/src/i18n/fr/tree.json b/client/src/i18n/fr/tree.json index 69c08a9cc5..ab10f8df95 100644 --- a/client/src/i18n/fr/tree.json +++ b/client/src/i18n/fr/tree.json @@ -139,6 +139,7 @@ "ROLE_MANAGEMENT":"Gestion des rôles", "ROOT":"Racine", "RUBRIC_MANAGEMENT":"Gestion Rubriques", + "RUMER_REPORT" : "Rumer", "SERVICE":"Services", "SIMPLE_VOUCHER":"Bordereau de transfert", "STAFFING_INDICES_MANAGEMENT" : "Gestion des indices de paie", diff --git a/client/src/modules/reports/generate/rumer_report/rumer_report.config.js b/client/src/modules/reports/generate/rumer_report/rumer_report.config.js new file mode 100644 index 0000000000..aca0c80328 --- /dev/null +++ b/client/src/modules/reports/generate/rumer_report/rumer_report.config.js @@ -0,0 +1,83 @@ +angular.module('bhima.controllers') + .controller('rumer_reportController', rumerReportController); + +rumerReportController.$inject = [ + '$sce', 'NotifyService', 'BaseReportService', 'AppCache', 'reportData', '$state', + 'LanguageService', +]; + +function rumerReportController($sce, Notify, SavedReports, AppCache, reportData, $state, Languages) { + const vm = this; + const cache = new AppCache('rumer_report'); + const reportUrl = 'reports/stock/rumer_report'; + + // default values + vm.reportDetails = { + includePurchaseEntry : 1, + }; + vm.previewGenerated = false; + + // check cached configuration + checkCachedConfiguration(); + + vm.onSelectDepot = depot => { + vm.reportDetails.depotUuid = depot.uuid; + vm.reportDetails.depot_text = depot.text; + }; + + vm.onSelectFiscalYear = (fiscalYear) => { + vm.reportDetails.fiscal_id = fiscalYear.id; + vm.reportDetails.fiscalYearStart = fiscalYear.start_date; + }; + + vm.onSelectPeriod = (period) => { + vm.reportDetails.period_id = period.id; + vm.reportDetails.end_date = period.end_date; + vm.reportDetails.start_date = period.start_date; + }; + + vm.clear = key => { + delete vm[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 || {}); + } + +} diff --git a/client/src/modules/reports/generate/rumer_report/rumer_report.html b/client/src/modules/reports/generate/rumer_report/rumer_report.html new file mode 100644 index 0000000000..aa8afe178f --- /dev/null +++ b/client/src/modules/reports/generate/rumer_report/rumer_report.html @@ -0,0 +1,63 @@ + + + +
+
+
+

REPORT.RUMER.TITLE

+

REPORT.RUMER.DESCRIPTION

+
+
+ +
+
+
+
+ REPORT.UTIL.OPTIONS +
+ +
+ +
+ + + + + + + + + + + + + + + + REPORT.UTIL.PREVIEW + +
+
+
+
+
+
diff --git a/client/src/modules/reports/reports.routes.js b/client/src/modules/reports/reports.routes.js index 0774dc9620..f67e30d808 100644 --- a/client/src/modules/reports/reports.routes.js +++ b/client/src/modules/reports/reports.routes.js @@ -52,6 +52,7 @@ angular.module('bhima.routes') 'stock_movement_report', 'stock_expiration_report', 'stock_changes', + 'rumer_report', ]; function resolveReportData($stateParams, SavedReports) { diff --git a/server/config/routes.js b/server/config/routes.js index 57db6be706..1872b2da25 100644 --- a/server/config/routes.js +++ b/server/config/routes.js @@ -846,6 +846,7 @@ exports.configure = function configure(app) { app.get('/reports/stock/sheet', stockReports.stockSheetReport); app.get('/reports/stock/value', stockReports.stockValue); app.get('/reports/stock/monthly_consumption', stockReports.monthlyConsumption.report); + app.get('/reports/stock/rumer_report', stockReports.rumer.report); // stock receipts API app.get('/receipts/stock/:uuid', stockReports.renderStockReceipt); diff --git a/server/controllers/stock/index.js b/server/controllers/stock/index.js index 144d08a048..908c40b8eb 100644 --- a/server/controllers/stock/index.js +++ b/server/controllers/stock/index.js @@ -193,7 +193,6 @@ function updateQuantityInStockAfterMovement(inventoryUuids, mvmtDate, depotUuid) */ async function insertNewStock(session, params) { const transaction = db.transaction(); - const identifier = uuid(); const documentUuid = uuid(); const period = await Fiscal.lookupFiscalYearByDate(params.movement.date); diff --git a/server/controllers/stock/reports/index.js b/server/controllers/stock/reports/index.js index a3052e0161..ae076352d9 100644 --- a/server/controllers/stock/reports/index.js +++ b/server/controllers/stock/reports/index.js @@ -154,3 +154,4 @@ exports.stockChangesReport = stockChangesReport; exports.stockAdjustmentReceipt = stockAdjustmentReceipt; exports.stockExitAggregateConsumptionReceipt = stockExitAggregateConsumptionReceipt; exports.monthlyConsumption = require('./stock/monthly_consumption'); +exports.rumer = require('./stock/rumer'); diff --git a/server/controllers/stock/reports/rumer.report.handlebars b/server/controllers/stock/reports/rumer.report.handlebars new file mode 100644 index 0000000000..84a722ee54 --- /dev/null +++ b/server/controllers/stock/reports/rumer.report.handlebars @@ -0,0 +1,54 @@ +{{> head title="REPORT.MONTHLY_CONSUMPTION.TITLE" }} + + + {{> header}} + + +
+
+ +

+ {{translate 'REPORT.RUMER.TITLE'}} +

+ +
{{translate 'REPORT.RUMER.DESCRIPTION'}}
+ +

{{params.depot_text}}

+

{{date params.start_date}} - {{date params.end_date}}

+ + + + + + + + {{#each header as | key |}} + + {{/each}} + + + + + + + {{#each configurationData}} + + + + + {{#each dailyConsumption }} + + {{/each}} + + + + {{/each}} + +
{{translate 'FORM.LABELS.INVENTORY'}} {{translate 'REPORT.RUMER.STOCK_BEGINNING'}} {{translate 'REPORT.RUMER.TOTAL_ENTRIES'}} {{key}}{{translate 'FORM.LABELS.TOTAL'}} {{translate 'REPORT.RUMER.STOCK_END'}}
{{ inventoryText }} {{ quantityOpening }} {{ quantityTotalEntry }} + {{#if value}} + {{ value }} + {{/if}} + {{ quantityTotalExit }} {{ quantityEnding }}
+
+
+ diff --git a/server/controllers/stock/reports/stock/rumer.js b/server/controllers/stock/reports/stock/rumer.js new file mode 100644 index 0000000000..b60cbfd303 --- /dev/null +++ b/server/controllers/stock/reports/stock/rumer.js @@ -0,0 +1,176 @@ +const _ = require('lodash'); +const moment = require('moment'); + +const db = require('../../../../lib/db'); +const core = require('../../core'); +const ReportManager = require('../../../../lib/ReportManager'); + +const TEMPLATE = './server/controllers/stock/reports/rumer.report.handlebars'; + +exports.report = report; + +// default report parameters +const DEFAULT_PARAMS = { + csvKey : 'rumer', + filename : 'TREE.RUMER', + orientation : 'landscape', +}; + +/** + * @method report + * + * @description + * This method builds the RUMER (Drug Usage and Recipe Register) report by month JSON, PDF, or HTML + * file to be sent to the client. + * + * GET /reports/stock/rumer_report + */ +async function report(req, res, next) { + const params = req.query; + + const data = {}; + const headerReport = []; + const configurationData = []; + + params.start_date = moment(new Date(params.start_date)).format('YYYY-MM-DD'); + params.end_date = moment(new Date(params.end_date)).format('YYYY-MM-DD'); + + const startDate = parseInt(moment(params.start_date).format('DD'), 10); + const endDate = parseInt(moment(params.end_date).format('DD'), 10); + + const lastDayPreviousMonth = moment(params.start_date).subtract(1, 'day').format('YYYY-MM-DD'); + + for (let i = startDate; i <= endDate; i++) { + headerReport.push(i); + } + + data.params = params; + data.header = headerReport; + + const parameterOpeningStock = { + depot_uuid : params.depotUuid, + dateTo : lastDayPreviousMonth, + includeEmptyLot : 1, + }; + + const parameterEndingStock = { + depot_uuid : params.depotUuid, + dateTo : params.end_date, + includeEmptyLot : 1, + }; + + _.defaults(params, DEFAULT_PARAMS); + + try { + const reporting = new ReportManager(TEMPLATE, req.session, params); + + const sqlDailyConsumption = ` + SELECT BUID(inv.uuid) AS uuid, inv.code, inv.text AS inventory_text, + SUM(sm.quantity) AS quantity, DATE(sm.date) AS dateMovement + FROM stock_movement AS sm + JOIN lot AS l ON l.uuid = sm.lot_uuid + JOIN inventory AS inv ON inv.uuid = l.inventory_uuid + WHERE sm.depot_uuid = ? AND DATE(sm.date) >= DATE(?) AND DATE(sm.date) <= DATE(?) + AND sm.is_exit = 1 + GROUP BY inv.uuid, dateMovement; + `; + + const sqlMonthlyConsumption = ` + SELECT BUID(inv.uuid) AS uuid, inv.code, inv.text AS inventory_text, SUM(sm.quantity) AS quantity, sm.date + FROM stock_movement AS sm + JOIN lot AS l ON l.uuid = sm.lot_uuid + JOIN inventory AS inv ON inv.uuid = l.inventory_uuid + WHERE sm.depot_uuid = ? AND DATE(sm.date) >= DATE(?) AND DATE(sm.date) <= DATE(?) + AND sm.is_exit = 1 + GROUP BY inv.uuid; + `; + + const sqlStockEntryMonth = ` + SELECT BUID(inv.uuid) AS uuid, inv.code, inv.text AS inventory_text, SUM(sm.quantity) AS quantity, + sm.date, sm.is_exit + FROM stock_movement AS sm + JOIN lot AS l ON l.uuid = sm.lot_uuid + JOIN inventory AS inv ON inv.uuid = l.inventory_uuid + WHERE sm.depot_uuid = ? AND DATE(sm.date) >= DATE(?) AND DATE(sm.date) <= DATE(?) + AND sm.is_exit = 0 + GROUP BY inv.uuid; + `; + + const [inventoriesOpening, inventoriesConsumed, + inventoriesEntry, monthlyConsumption, inventoriesEnding] = await Promise.all([ + core.getInventoryQuantityAndConsumption(parameterOpeningStock), + db.exec(sqlDailyConsumption, [db.bid(params.depotUuid), params.start_date, params.end_date]), + db.exec(sqlStockEntryMonth, [db.bid(params.depotUuid), params.start_date, params.end_date]), + db.exec(sqlMonthlyConsumption, [db.bid(params.depotUuid), params.start_date, params.end_date]), + core.getInventoryQuantityAndConsumption(parameterEndingStock), + ]); + + inventoriesEnding.forEach(inventory => { + configurationData.push({ + inventoryUuid : inventory.inventory_uuid, + inventoryText : inventory.text, + quantityOpening : 0, + quantityTotalEntry : 0, + quantityTotalExit : 0, + quantityEnding : inventory.quantity, + }); + }); + + configurationData.forEach(inventory => { + const dailyConsumption = []; + for (let i = startDate; i <= endDate; i++) { + dailyConsumption.push({ value : 0, index : i }); + } + + inventoriesConsumed.forEach(consumed => { + if (inventory.inventoryUuid === consumed.uuid) { + const dateConsumption = parseInt(moment(consumed.dateMovement).format('DD'), 10); + dailyConsumption.forEach(d => { + if (d.index === dateConsumption) { + d.value = consumed.quantity; + } + }); + } + }); + + inventory.dailyConsumption = dailyConsumption; + }); + + if (inventoriesOpening.length) { + configurationData.forEach(inventory => { + inventoriesOpening.forEach(opening => { + if (inventory.inventoryUuid === opening.inventory_uuid) { + inventory.quantityOpening = opening.quantity; + } + }); + }); + } + + if (inventoriesEntry.length) { + configurationData.forEach(inventory => { + inventoriesEntry.forEach(entry => { + if (inventory.inventoryUuid === entry.uuid) { + inventory.quantityTotalEntry = entry.quantity; + } + }); + }); + } + + if (monthlyConsumption.length) { + configurationData.forEach(inventory => { + monthlyConsumption.forEach(exit => { + if (inventory.inventoryUuid === exit.uuid) { + inventory.quantityTotalExit = exit.quantity; + } + }); + }); + } + + data.configurationData = configurationData; + + const result = await reporting.render(data); + res.set(result.headers).send(result.report); + } catch (e) { + next(e); + } +} diff --git a/server/models/bhima.sql b/server/models/bhima.sql index 30ffd8decb..58cb225e19 100644 --- a/server/models/bhima.sql +++ b/server/models/bhima.sql @@ -166,8 +166,10 @@ INSERT INTO unit VALUES (290, '[SETTINGS] Settings', 'TREE.STOCK_SETTINGS', 'Stock Settings', 160, '/stock/setting'), (291, '[Stock] Dashboard', 'TREE.STOCK_DASHBOARD','Stock Dashboard', 160,'/stock/dashboard'), (292, '[Stock] Changes Report', 'REPORT.STOCK_CHANGES.TITLE', 'Stock Changes Report', 282, '/reports/stock_changes'), - (293, 'Aggregated consumption','TREE.AGGREGATED_STOCK_CONSUMPTION','Aggregated consumption',160,'/stock/aggregated_consumption'), - (294, 'Duplicate Lots','TREE.DUPLICATE_LOTS','The stock lots duplicates list',160,'/stock/lots/duplicates'); + (293, 'Aggregated consumption','TREE.AGGREGATED_STOCK_CONSUMPTION','Aggregated consumption', 160,'/stock/aggregated_consumption'), + (294, 'Duplicate Lots','TREE.DUPLICATE_LOTS','The stock lots duplicates list', 160,'/stock/lots/duplicates'), + (295, 'Rumer report','TREE.RUMER_REPORT','The rumer reports', 282,'/reports/rumer_report'); + -- Reserved system account type INSERT INTO `account_category` VALUES @@ -237,7 +239,8 @@ INSERT INTO `report` (`report_key`, `title_key`) VALUES ('invoiceRegistryReport', 'Invoice Registry as report'), ('stock_movement_report', 'REPORT.STOCK_MOVEMENT_REPORT.TITLE'), ('stock_expiration_report', 'REPORT.STOCK_EXPIRATION_REPORT.TITLE'), - ('stock_changes', 'REPORT.STOCK_CHANGES.TITLE'); + ('stock_changes', 'REPORT.STOCK_CHANGES.TITLE'), + ('rumer_report', 'REPORT.RUMER.TITLE'); -- Supported Languages INSERT INTO `language` VALUES diff --git a/server/models/migrations/next/migrate.sql b/server/models/migrations/next/migrate.sql index d0d3cbc913..64f34adc85 100644 --- a/server/models/migrations/next/migrate.sql +++ b/server/models/migrations/next/migrate.sql @@ -90,3 +90,14 @@ INSERT IGNORE INTO `exchange_rate` VALUES (3, 1, @EUR, 0.84, NOW()); @description: remove the origin_uuid column from the lot table. */ ALTER TABLE `lot` DROP COLUMN `origin_uuid`; + +/* + * @author: lomamech + * @date: 2021-04-26 + * @subject : Implement the RUMER report + */ +INSERT INTO unit VALUES + (295, 'Rumer report','TREE.RUMER_REPORT','The rumer reports', 282,'/reports/rumer_report'); + +INSERT INTO `report` (`report_key`, `title_key`) VALUES + ('rumer_report', 'REPORT.RUMER.TITLE');