diff --git a/client/src/i18n/en/report.json b/client/src/i18n/en/report.json index cbb1b43091..933cc7ce91 100644 --- a/client/src/i18n/en/report.json +++ b/client/src/i18n/en/report.json @@ -83,6 +83,11 @@ "SHOW_UNVERIFIED_TRANSACTIONS": "Include Unverified Transactions", "SHOW_UNVERIFIED_TRANSACTIONS_HELP": "Enable to include records from the Journal which have not been audited by the accountant as well as records from the General Ledger the accountant has verified. This view must be treated as a projection until the accountant has approved the Journal transactions and moved them to the General Ledger." }, + + "DEBTOR_SUMMARY" : { + "TITLE": "Debtors summary report", + "DESCRIPTION": "This report details the amount to paid in every inventory group for an invoice during a period, for a specific debor group" + }, "OPENING_BALANCE": "Opening Balance", "ORDER": { "LAST_TRANSACTION": "Date of Last Transaction", diff --git a/client/src/i18n/fr/report.json b/client/src/i18n/fr/report.json index 0620acf0c3..b92476793d 100644 --- a/client/src/i18n/fr/report.json +++ b/client/src/i18n/fr/report.json @@ -77,6 +77,10 @@ "SHOW_UNVERIFIED_TRANSACTIONS" : "Inclure les transactions non vérifiées", "SHOW_UNVERIFIED_TRANSACTIONS_HELP" : "L'activation de cette option inclut les données du Journal (données non verifiées) et celles du Grand Livre (données vérifiées). Ce rapport doit être consideré comme une simple projection jusqu'à ce que les données du journal soient postées." }, + "DEBTOR_SUMMARY" : { + "TITLE": "Rapport sommaire des débiteurs", + "DESCRIPTION": "Ce rapport détaille le montant à payer dans chaque groupe d'inventaire pour une facture pendant une période, pour un groupe debor spécifique" + }, "OPENING_BALANCE": "Balance d'ouverture", "ORDER": { "LAST_TRANSACTION": "Date de la dernière transaction", diff --git a/client/src/modules/reports/generate/debtorSummary/debtorSummary.config.js b/client/src/modules/reports/generate/debtorSummary/debtorSummary.config.js new file mode 100644 index 0000000000..ffba8d0b24 --- /dev/null +++ b/client/src/modules/reports/generate/debtorSummary/debtorSummary.config.js @@ -0,0 +1,65 @@ +angular.module('bhima.controllers') + .controller('debtorSummaryController', DebtorSummaryController); + +DebtorSummaryController.$inject = [ + '$sce', 'NotifyService', 'BaseReportService', + 'AppCache', 'reportData', '$state', +]; + +function DebtorSummaryController($sce, Notify, SavedReports, AppCache, reportData, $state) { + const vm = this; + const cache = new AppCache('debtorSummary'); + const reportUrl = 'reports/finance/debtorSummary'; + + vm.previewGenerated = false; + vm.reportDetails = {}; + + vm.clearPreview = function clearPreview() { + vm.previewGenerated = false; + vm.previewResult = null; + }; + + vm.onSelectDebtor = (debtorGroup) => { + vm.reportDetails.group_uuid = debtorGroup.uuid; + }; + + vm.preview = function preview(form) { + if (form.$invalid) { + Notify.danger('FORM.ERRORS.RECORD_ERROR'); + return 0; + } + + + // update cached configuration + cache.reportDetails = angular.copy(vm.reportDetails); + + 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); + }; + + checkCachedConfiguration(); + + function checkCachedConfiguration() { + if (cache.reportDetails) { + vm.reportDetails = angular.copy(cache.reportDetails); + } + } +} diff --git a/client/src/modules/reports/generate/debtorSummary/debtorSummary.html b/client/src/modules/reports/generate/debtorSummary/debtorSummary.html new file mode 100644 index 0000000000..9934647deb --- /dev/null +++ b/client/src/modules/reports/generate/debtorSummary/debtorSummary.html @@ -0,0 +1,49 @@ + + + +
+
+
+

REPORT.DEBTOR_SUMMARY.TITLE

+

REPORT.DEBTOR_SUMMARY.DESCRIPTION

+
+
+ +
+
+
+
+ REPORT.UTIL.OPTIONS +
+
+
+ + + + + + + + + + + + REPORT.UTIL.PREVIEW + +
+
+
+
+
+
\ No newline at end of file diff --git a/client/src/modules/reports/reports.routes.js b/client/src/modules/reports/reports.routes.js index 56f2144cf8..6004ee718e 100644 --- a/client/src/modules/reports/reports.routes.js +++ b/client/src/modules/reports/reports.routes.js @@ -35,6 +35,7 @@ angular.module('bhima.routes') 'indicatorsReport', 'visit_report', 'monthlyBalance', + 'debtorSummary', ]; $stateProvider diff --git a/server/config/routes.js b/server/config/routes.js index 85d962ca50..00a3aa09dc 100644 --- a/server/config/routes.js +++ b/server/config/routes.js @@ -125,6 +125,7 @@ const accountReferenceType = require('../controllers/finance/accounts/accountRef const indicators = require('../controllers/finance/indicator'); const breakEvenReference = require('../controllers/finance/breakEvenReference'); +const debotrSummaryReport = require('../controllers/finance/reports/debtors/summaryReport'); // periods const period = require('../controllers/finance/period'); @@ -416,7 +417,7 @@ exports.configure = function configure(app) { app.get('/reports/finance/break_even', financeReports.breakEven.report); app.get('/reports/finance/break_even_fee_center', financeReports.breakEvenFeeCenter.report); app.get('/reports/finance/operating', financeReports.operating.document); - + app.get('/reports/finance/debtorSummary', debotrSummaryReport.summaryReport); // visits reports app.get('/reports/visits', medicalReports.visitsReports.document); diff --git a/server/controllers/finance/reports/debtors/summaryReport.handlebars b/server/controllers/finance/reports/debtors/summaryReport.handlebars new file mode 100644 index 0000000000..0766d1ffdc --- /dev/null +++ b/server/controllers/finance/reports/debtors/summaryReport.handlebars @@ -0,0 +1,67 @@ +{{> head title="REPORT.CLIENTS.TITLE"}} + + +
+ {{> header }} + +

+ {{translate "REPORT.DEBTOR_SUMMARY.TITLE"}} +

+ +

+ {{ debtorGroup.name }} +

+
+ {{date dateFrom "MMMM YYYY"}} + - + {{date dateTo "MMMM YYYY"}} +
+ +
+ + + + + + + + + {{#each ./inventoryGroups as | s |}} + + + {{/each}} + + + + + + {{#each ./data as |invoice| }} + + + + + + + + {{#each invoice.inventoryGroups as | amount |}} + + {{/each}} + + + + {{/each}} + + + + + + {{#each inventoryGroups as | s |}} + + {{/each}} + + +
{{translate "FORM.LABELS.NO"}}{{translate "FORM.LABELS.DATE"}}{{translate "TABLE.COLUMNS.REFERENCE"}}{{translate "TABLE.COLUMNS.NAME"}}{{translate "FORM.LABELS.SERVICE"}} +
{{ s.name }}
{{translate "FORM.LABELS.TOTAL"}}
{{ add @index 1}}{{ date invoice.date "DD/MM/YYYY"}}{{ invoice.invRef}}{{ invoice.debtorName}}{{ invoice.serviceName}}{{amount}}{{currency invoice.total ../metadata.enterprise.currency_id}}
{{ translate 'FORM.LABELS.TOTAL'}}{{ currency s.total ../metadata.enterprise.currency_id}}{{currency gobalSum ../metadata.enterprise.currency_id}}
+
+
+ diff --git a/server/controllers/finance/reports/debtors/summaryReport.js b/server/controllers/finance/reports/debtors/summaryReport.js new file mode 100644 index 0000000000..87fe221ec3 --- /dev/null +++ b/server/controllers/finance/reports/debtors/summaryReport.js @@ -0,0 +1,112 @@ +/** + * @overview ./finance/reports/debtors/summaryReport.js + * +*/ + +const _ = require('lodash'); +const ReportManager = require('../../../../lib/ReportManager'); +const db = require('../../../../lib/db'); +const util = require('../../../../lib/util'); + +module.exports.summaryReport = summaryReport; + +// path to the template to render +const TEMPLATE = './server/controllers/finance/reports/debtors/summaryReport.handlebars'; + +const DEFAULT_OPTIONS = { + csvKey : 'debtors', + orientation : 'landscape', + footerRight : '[page] / [toPage]', + footerFontSize : '7', +}; + +/** + * @method summaryReport + * + * @description + * The HTTP interface which actually creates the report. + */ +async function summaryReport(req, res, next) { + try { + const qs = _.extend(req.query, DEFAULT_OPTIONS); + const { dateFrom, dateTo } = req.query; + const groupUuid = req.query.group_uuid; + const metadata = _.clone(req.session); + + const report = new ReportManager(TEMPLATE, metadata, qs); + + const inventoryGroupMap = {}; + const emptyArray = []; + const inventoryGroupIndexMap = {}; + let gobalSum = 0; + + const invoiceSql = ` + SELECT BUID(i.uuid) AS invoice_uuid, i.date, dm.text AS invRef, ent.text AS debtorRef, + d.text, SUM(it.transaction_price) AS amount, i.cost as total, BUID(i.debtor_uuid) AS debtor_uuid, + invg.name as inventoryGroupName, BUID(invg.uuid) as inventoryGroupUuid, inv.text as inventoryName, i.service_id, + s.name as serviceName + FROM invoice i + JOIN invoice_item it ON it.invoice_uuid = i.uuid + JOIN inventory inv ON inv.uuid = it.inventory_uuid + JOIN inventory_group invg ON invg.uuid = inv.group_uuid + JOIN debtor d ON d.uuid = i.debtor_uuid + JOIN debtor_group dg ON dg.uuid = d.group_uuid + JOIN entity_map ent ON ent.uuid = d.uuid + JOIN document_map dm ON dm.uuid = i.uuid + JOIN service s ON s.id = i.service_id + WHERE dg.uuid = ? AND (i.date BETWEEN ? AND ?) AND i.reversed = 0 + GROUP BY invg.uuid, i.uuid + ORDER BY i.date + `; + + + const inventoryGroupsSql = ` + SELECT DISTINCT x.inventoryGroupUuid as id, x.inventoryGroupName as name + FROM (${invoiceSql}) as x + `; + + const debtorGroup = await db.one('SELECT name FROM debtor_group WHERE uuid=?', db.bid(groupUuid)); + const inventoryGroups = await db.exec(inventoryGroupsSql, [db.bid(groupUuid), dateFrom, dateTo]); + + // initilisation + inventoryGroups.forEach((s, index) => { + inventoryGroupMap[s.id] = s; + inventoryGroupMap[s.id].total = 0; + emptyArray.push(null); + inventoryGroupIndexMap[s.id] = index; + }); + + // let get the list of invoices for this group + const invoices = await db.exec(invoiceSql, [db.bid(groupUuid), dateFrom, dateTo]); + + const invoicesList = _.groupBy(invoices, 'invoice_uuid'); + const data = []; + // let loop each invoice attribute each invoice item to it inventoryGroup + Object.keys(invoicesList).forEach(invKey => { + const invItems = invoicesList[invKey]; + const record = { inventoryGroups : _.clone(emptyArray) }; + invItems.forEach(item => { + record.date = item.date; + record.invRef = item.invRef; + record.debtorRef = item.debtorRef; + record.total = item.total; + record.debtorName = item.text; + record.serviceName = item.serviceName; + record.inventoryGroups[inventoryGroupIndexMap[item.inventoryGroupUuid]] = item.amount; + inventoryGroupMap[item.inventoryGroupUuid].total += item.amount; + }); + data.push(record); + }); + + data.forEach(record => { + gobalSum += record.total; + }); + // then let render the report + const result = await report.render({ + debtorGroup, inventoryGroups, data, dateFrom, dateTo, gobalSum : util.roundDecimal(gobalSum, 2), + }); + res.set(result.headers).send(result.report); + } catch (ex) { + next(ex); + } +} diff --git a/server/models/bhima.sql b/server/models/bhima.sql index dee0da1699..593258bccc 100644 --- a/server/models/bhima.sql +++ b/server/models/bhima.sql @@ -128,7 +128,8 @@ INSERT INTO unit VALUES (241, 'Entity Folder', 'ENTITY.MANAGEMENT', 'Entity Folder', 0, '/modules/entities', '/ENTITY_FOLDER'), (242, 'Entity Management','ENTITY.MANAGEMENT','',241,'/modules/entities','/entities'), (243, 'Entity Group', 'ENTITY.GROUP.TITLE', 'Entity Group', 241, '/modules/entity_group', '/entity_group'), - (244, 'Monthly Balance', 'TREE.MONTHLY_BALANCE', 'Monthly Balance', 144, '/modules/reports/monthlyBalance', '/reports/monthlyBalance'); + (244, 'Monthly Balance', 'TREE.MONTHLY_BALANCE', 'Monthly Balance', 144, '/modules/reports/monthlyBalance', '/reports/monthlyBalance'), + (245, 'Debtor summary report', 'REPORT.DEBTOR_SUMMARY.TITLE', 'Debtor summary report', 144, '/modules/reports/debtorSummary', '/reports/debtorSummary'); -- Reserved system account type INSERT INTO `account_category` VALUES @@ -180,7 +181,8 @@ INSERT INTO `report` (`id`, `report_key`, `title_key`) VALUES (31, 'indicatorsReport', 'TREE.INDICATORS_REPORT'), (32, 'visit_report', 'PATIENT_RECORDS.REPORT.VISITS'), (33, 'stock_entry', 'REPORT.STOCK.ENTRY_REPORT'), - (34, 'monthlyBalance', 'REPORT.MONTHLY_BALANCE.TITLE'); + (34, 'monthlyBalance', 'REPORT.MONTHLY_BALANCE.TITLE'), + (35, 'debtorSummary', 'REPORT.DEBTOR_SUMMARY.TITLE'); -- Supported Languages INSERT INTO `language` VALUES diff --git a/server/models/migrations/next/migrations.sql b/server/models/migrations/next/migrations.sql index 162fc95ef1..745b5b6f82 100644 --- a/server/models/migrations/next/migrations.sql +++ b/server/models/migrations/next/migrations.sql @@ -348,3 +348,15 @@ INSERT INTO unit VALUES INSERT INTO `report` (`id`, `report_key`, `title_key`) VALUES (34, 'monthlyBalance', 'REPORT.MONTHLY_BALANCE.TITLE'); +/* + @author:jeremielodi + @date: 2019-08-04 + @title : Debtors summary report, + @description: TThis report shows the amount to paid for each debtor of a given debtor group in each service during a period +*/ + +INSERT INTO `report` (`id`, `report_key`, `title_key`) VALUES +(35, 'debtorSummary', 'REPORT.DEBTOR_SUMMARY.TITLE'); + +INSERT INTO unit VALUES +(245, 'Debtor summary report', 'REPORT.DEBTOR_SUMMARY.TITLE', 'Debtor summary report', 144, '/modules/reports/debtorSummary', '/reports/debtorSummary');