Skip to content

Commit

Permalink
feat(assets): add registry exports
Browse files Browse the repository at this point in the history
Adds the asset management registry exports to the Assets module.  This
includes exporting to PDF, XLSX, and CSV.

Closes #6580.
  • Loading branch information
jniles committed May 3, 2022
1 parent 2de918b commit c2bd565
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 55 deletions.
20 changes: 20 additions & 0 deletions client/src/modules/assets/assets-registry.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@
<i class="fa fa-close"></i> <span translate>FORM.BUTTONS.CLEAR_GRID_CONFIGURATION</span>
</a>
</li>
<li role="separator" class="divider"></li>


<li role="menuitem">
<a data-method="export-pdf" ng-href="/assets/reports/registry?{{ AssetsCtrl.exportTo('pdf') }}" download="{{ 'TREE.ASSET_MANAGEMENT.TITLE' | translate }}">
<span class="fa fa-file-pdf-o"></span> <span translate>DOWNLOADS.PDF</span>
</a>
</li>

<li role="menuitem">
<a data-method="export-csv" ng-href="/assets/reports/registry?{{ AssetsCtrl.exportTo('csv') }}" download="{{ 'TREE.ASSET_MANAGEMENT.TITLE' | translate }}">
<span class="fa fa-file-excel-o"></span> <span translate>DOWNLOADS.CSV</span>
</a>
</li>

<li role="menuitem">
<a data-method="export-xlsx" ng-href="/assets/reports/registry?{{ AssetsCtrl.exportTo('xlsx') }}" download="{{ 'TREE.ASSET_MANAGEMENT.TITLE' | translate }}">
<span class="fa fa-file-excel-o"></span> <span translate>DOWNLOADS.EXCEL</span>
</a>
</li>

<li role="separator" class="divider"></li>
<li role="menuitem" bh-require-enterprise-setting="barcodes">
Expand Down
17 changes: 3 additions & 14 deletions client/src/modules/assets/assets-registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ AssetsRegistryController.$inject = [

/**
* Assets Registry Controller
*
* @description
* This module is a registry page for assets
*/
function AssetsRegistryController(
Expand Down Expand Up @@ -247,25 +249,12 @@ function AssetsRegistryController(
StockModal.openAssignmentHistoric({ uuid, depotUuid });
};

vm.downloadExcel = () => {
const filterOpts = stockLotFilters.formatHTTP();
const defaultOpts = {
renderer : 'xlsx',
lang : Languages.key,
renameKeys : true,
displayNames : gridColumns.getDisplayNames(),
};
// combine options
const options = angular.merge(defaultOpts, filterOpts);
// return serialized options
return $httpParamSerializer(options);
};

vm.exportTo = (renderer) => {
const filterOpts = stockLotFilters.formatHTTP();
const defaultOpts = {
renderer,
lang : Languages.key,
displayNames : gridColumns.getDisplayNames(),
};
const options = angular.merge(defaultOpts, filterOpts);
// return serialized options
Expand Down
2 changes: 1 addition & 1 deletion client/src/modules/assets/assets-registry.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ AssetsRegistryService.$inject = [
];

/**
* This service encapsulate some common method of stock lots registry with the aims
* This service encapsulate some common methods of the Asset registry with the aims
* of reducing lines in registry.js
*/
function AssetsRegistryService(uiGridConstants, Session) {
Expand Down
6 changes: 4 additions & 2 deletions server/config/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const stock = require('../controllers/stock');
const stockReports = require('../controllers/stock/reports');
const stockSetting = require('../controllers/stock/setting');
const shipment = require('../controllers/asset_management/shipment');
const assetReports = require('../controllers/asset_management/assets');
const assets = require('../controllers/asset_management/assets');

// finance routes
const trialBalance = require('../controllers/finance/trialBalance');
Expand Down Expand Up @@ -247,6 +247,8 @@ exports.configure = function configure(app) {
app.get('/accounts/template', accounts.importing.downloadTemplate);
app.post('/accounts/import', upload.middleware('csv', 'file'), accounts.importing.importAccounts);

app.use('/assets', assets);

// API for account routes crud
app.get('/accounts', accounts.list);
app.get('/accounts/:id', accounts.detail);
Expand Down Expand Up @@ -894,7 +896,7 @@ exports.configure = function configure(app) {
app.get('/reports/stock/rumer_report', stockReports.rumer.report);
app.get('/reports/stock/assign', stockReports.stockAssignReport);

app.get('/reports/assets/needed_inventory_scans', assetReports.neededInventoryScansReport);
app.get('/reports/assets/needed_inventory_scans', assets.neededInventoryScansReport);

// stock receipts API
app.get('/receipts/stock/:uuid', stockReports.renderStockReceipt);
Expand Down
111 changes: 107 additions & 4 deletions server/controllers/asset_management/assets/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,108 @@
const inventoryScansReports = require('./reports/needed_inventory_scans');
const router = require('express').Router();

module.exports = {
neededInventoryScansReport : inventoryScansReports.neededInventoryScansReport,
};
const { neededInventoryScansReport } = require('./reports/needed_inventory_scans');

const {
_, ReportManager, Stock, formatFilters, ASSETS_REGISTRY_TEMPLATE, stockStatusLabelKeys,
} = require('../../stock/reports/common');

const i18n = require('../../../lib/helpers/translate');

/**
* @method assetRegistryReport
*
* @description
* This method builds the assets report as either a JSON, PDF, or HTML
* file to be sent to the client.
*
* GET /assets/reports/registry
*/
async function assetRegistryReport(req, res, next) {
const { lang } = req.query;

const optionReport = _.extend(req.query, { filename : 'TREE.ASSETS_REGISTRY' });

try {
const options = req.query;

const report = new ReportManager(ASSETS_REGISTRY_TEMPLATE, req.session, optionReport);

if (options.defaultPeriod) {
options.defaultPeriodEntry = options.defaultPeriod;
delete options.defaultPeriod;
}

options.month_average_consumption = req.session.stock_settings.month_average_consumption;
options.average_consumption_algo = req.session.stock_settings.average_consumption_algo;

const purgeKeys = [
'NO_CONSUMPTION', 'S_MONTH', 'S_RISK', 'S_RISK_QUANTITY',
'S_MAX', 'S_MIN', 'S_SEC', 'S_Q',
'at_risk_of_stock_out', 'cmms', 'color',
'default_purchase_interval', 'delay', 'depot_uuid',
'enterprisePurchaseInterval', 'exhausted', 'expired',
'inventory_uuid', 'lifetime_lot', 'min_delay',
'min_months_security_stock', 'mvt_quantity', 'near_expiration',
'purchase_interval', 'tag_name', 'tracking_consumption',
'tracking_expiration', 'wac',
];

const dateKeys = ['min_stock_date', 'max_stock_date'];

options.is_asset = 1;

const rows = (await Stock.getLotsDepot(null, options))
.map(row => {
const item = _.omit(row, purgeKeys);

// Sanitize invalid dates
dateKeys.forEach(key => {
if (JSON.stringify(item[key]) === 'null') {
item[key] = '';
}
});

// translate the status field
if (item.status in stockStatusLabelKeys) {
item.status = i18n(lang)(stockStatusLabelKeys[item.status]);
}
return item;
});

const data = {};

data.rows = rows;
data.csv = rows;

data.filters = _.uniqBy(formatFilters(options), 'field');

// group by depot
const groupedDepots = _.groupBy(rows, d => d.depot_text);
const depots = {};

Object.keys(groupedDepots).sort(compare).forEach(d => {
depots[d] = _.sortBy(groupedDepots[d], line => String(line.text).toLocaleLowerCase());
});

data.depots = depots;

const result = await report.render(data);

res.set(result.headers).send(result.report);
} catch (e) {
next(e);
}
}

function compare(a, b) {
return a.localeCompare(b);
}

/**
* @description
* This is the base "assets/" route to render the asset registry report.
*/
router.get('/reports/registry', assetRegistryReport);

router.neededInventoryScansReport = neededInventoryScansReport;
module.exports = router;
86 changes: 86 additions & 0 deletions server/controllers/stock/reports/assets_registry.report.handlebars
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{{> head title="TREE.ASSETS_REGISTRY" }}

<body>

{{> header}}

<!-- body -->
<!-- page title -->
<h2 class="text-center text-uppercase">
{{translate 'TREE.ASSETS_REGISTRY'}}
</h2>

<h4 class="text-center">
{{date}}
</h4>

<!-- filters -->
{{> filterbar filters=filters }}

<!-- list of data -->
<table class="table table-condensed table-bordered table-report">
<thead>
<tr>
<th>{{translate 'STOCK.CODE'}}</th>
<th>{{translate 'STOCK.INVENTORY'}}</th>
<th>{{translate 'TABLE.COLUMNS.INVENTORY_GROUP'}}</th>
<th>{{translate 'ASSET.ASSET_LABEL'}}</th>
<th>{{translate 'STOCK.QUANTITY'}}</th>
<th>{{translate 'STOCK.UNIT_COST'}}</th>
<th>{{translate 'STOCK.ENTRY_DATE'}}</th>
<th>{{translate 'TABLE.COLUMNS.REFERENCE'}}</th>
<th>{{translate 'FORM.LABELS.REFERENCE_NUMBER'}}</th>
<th>{{translate 'FORM.LABELS.MANUFACTURER_BRAND'}}</th>
<th>{{translate 'FORM.LABELS.MANUFACTURER_MODEL'}}</th>
<th>{{translate 'FORM.LABELS.SERIAL_NUMBER'}}</th>
<th>{{translate 'TABLE.COLUMNS.STATUS'}}</th>
<th title={{translate 'LOTS.ASSIGNMENT_CREATED'}}>{{translate 'ENTITY.ASSIGNED_TO'}}</th>
</tr>
</thead>
<tbody>
<!-- for each depots which contains lots -->
{{#each depots as | items name |}}

<!-- this is the depot group header -->
<tr style="border:none">
<th style="border:none; border-bottom: solid black 2px;" class="text-uppercase" colspan="10">
{{ name }}
</th>

<th colspan="5" style="border:none; border-bottom: solid black 2px;" class="text-right">
({{ items.length }} {{ translate "TABLE.AGGREGATES.RECORDS" }})
</th>
</tr>

<!-- these are the items for each group -->
{{#each items as | item | }}
<tr>
<td>{{code}}</td>
<td>{{text}}</td>
<td>{{group_name}}</td>
<td>{{label}}</td>
<td class="text-right">{{quantity}}</td>
<td class="text-right">{{unit_cost}}</td>
<td class="text-right">{{date entry_date}}</td>
<td class="text-right">{{documentReference}}</td>
<td class="text-right">{{reference_number}}</td>
<td class="text-right">{{manufacturer_brand}}</td>
<td class="text-right">{{manufacturer_model}}</td>
<td class="text-right">{{serial_number}}</td>
<td class="text-right">{{status}}</td>
<td class="text-right">{{assigned_to_name}}</td>
</tr>
{{else}}
{{> emptyTable columns=15}}
{{/each}}

<!-- blank row -->
{{#unless @last }}
<!-- blank line -->
<tr style="border:none;">
<th style="border:none;"></th>
</tr>
{{/unless}}
{{/each}}
</tbody>
</table>
74 changes: 41 additions & 33 deletions server/controllers/stock/reports/common.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,49 @@
// Constants
const BASE_PATH = './server/controllers/stock/reports';
const path = require('path');

// shortcut: uses the name of the file and adds the path and extension
const mkPath = name => path.join(BASE_PATH, name).concat('.handlebars');

// receipts
const STOCK_EXIT_PATIENT_TEMPLATE = `${BASE_PATH}/stock_exit_patient.receipt.handlebars`;
const POS_STOCK_EXIT_PATIENT_TEMPLATE = `${BASE_PATH}/stock_exit_patient.receipt.pos.handlebars`;
const STOCK_EXIT_SERVICE_TEMPLATE = `${BASE_PATH}/stock_exit_service.receipt.handlebars`;
const POS_STOCK_EXIT_SERVICE_TEMPLATE = `${BASE_PATH}/stock_exit_service.receipt.pos.handlebars`;
const STOCK_EXIT_DEPOT_TEMPLATE = `${BASE_PATH}/stock_exit_depot.receipt.handlebars`;
const POS_STOCK_EXIT_DEPOT_TEMPLATE = `${BASE_PATH}/stock_exit_depot.receipt.pos.handlebars`;
const STOCK_EXIT_LOSS_TEMPLATE = `${BASE_PATH}/stock_exit_loss.receipt.handlebars`;
const POS_STOCK_EXIT_LOSS_TEMPLATE = `${BASE_PATH}/stock_exit_loss.receipt.pos.handlebars`;
const STOCK_ASSIGN_TEMPLATE = `${BASE_PATH}/stock/assignment/stock_assign.receipt.handlebars`;
const STOCK_ASSIGN_REGISTRY_TEMPLATE = `${BASE_PATH}/stock/assignment/stock_assign.registry.handlebars`;
const STOCK_CONSUMPTION_GRAPTH_TEMPLATE = `${BASE_PATH}/stock_consumption_graph.handlebars`;
const STOCK_MOVEMENT_REPORT_TEMPLATE = `${BASE_PATH}/stock_movement_report.handlebars`;
const LOT_BARCODE_TEMPLATE = `${BASE_PATH}/stock/lot_barcode/lot_barcode.handlebars`;

const STOCK_ENTRY_DEPOT_TEMPLATE = `${BASE_PATH}/stock_entry_depot.receipt.handlebars`;
const STOCK_ENTRY_PURCHASE_TEMPLATE = `${BASE_PATH}/stock_entry_purchase.receipt.handlebars`;
const STOCK_ENTRY_INTEGRATION_TEMPLATE = `${BASE_PATH}/stock_entry_integration.receipt.handlebars`;
const STOCK_ENTRY_DONATION_TEMPLATE = `${BASE_PATH}/stock_entry_donation.receipt.handlebars`;
const STOCK_ADJUSTMENT_TEMPLATE = `${BASE_PATH}/stock_adjustment.receipt.handlebars`;

const STOCK_AGGREGATE_CONSUMPTION_TEMPLATE = `${BASE_PATH}/stock_aggregate_consumption.receipt.handlebars`;
const STOCK_EXIT_PATIENT_TEMPLATE = mkPath('stock_exit_patient.receipt.handlebars');

const POS_STOCK_EXIT_PATIENT_TEMPLATE = mkPath('stock_exit_patient.receipt.pos');
const STOCK_EXIT_SERVICE_TEMPLATE = mkPath('stock_exit_service.receipt');
const POS_STOCK_EXIT_SERVICE_TEMPLATE = mkPath('stock_exit_service.receipt.pos');
const STOCK_EXIT_DEPOT_TEMPLATE = mkPath('stock_exit_depot.receipt');
const POS_STOCK_EXIT_DEPOT_TEMPLATE = mkPath('stock_exit_depot.receipt.pos');
const STOCK_EXIT_LOSS_TEMPLATE = mkPath('/stock_exit_loss.receipt');
const POS_STOCK_EXIT_LOSS_TEMPLATE = mkPath('stock_exit_loss.receipt.pos');
const STOCK_ASSIGN_TEMPLATE = mkPath('stock/assignment/stock_assign.receipt');
const STOCK_ASSIGN_REGISTRY_TEMPLATE = mkPath('/stock/assignment/stock_assign.registry');
const STOCK_CONSUMPTION_GRAPTH_TEMPLATE = mkPath('/stock_consumption_graph');
const STOCK_MOVEMENT_REPORT_TEMPLATE = mkPath('/stock_movement_report');
const LOT_BARCODE_TEMPLATE = mkPath('/stock/lot_barcode/lot_barcode');

const STOCK_ENTRY_DEPOT_TEMPLATE = mkPath('/stock_entry_depot.receipt');
const STOCK_ENTRY_PURCHASE_TEMPLATE = mkPath('/stock_entry_purchase.receipt');
const STOCK_ENTRY_INTEGRATION_TEMPLATE = mkPath('/stock_entry_integration.receipt');
const STOCK_ENTRY_DONATION_TEMPLATE = mkPath('/stock_entry_donation.receipt');
const STOCK_ADJUSTMENT_TEMPLATE = mkPath('/stock_adjustment.receipt');

const STOCK_AGGREGATE_CONSUMPTION_TEMPLATE = mkPath('/stock_aggregate_consumption.receipt');

// 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`;
const STOCK_LOTS_REPORT_TEMPLATE = `${BASE_PATH}/stock_lots.report.handlebars`;
const STOCK_MOVEMENTS_REPORT_TEMPLATE = `${BASE_PATH}/stock_movements.report.handlebars`;
const STOCK_INLINE_MOVEMENTS_REPORT_TEMPLATE = `${BASE_PATH}/stock_inline_movements.report.handlebars`;
const STOCK_INVENTORIES_REPORT_TEMPLATE = `${BASE_PATH}/stock_inventories.report.handlebars`;
const STOCK_SHEET_REPORT_TEMPLATE = `${BASE_PATH}/stock_sheet.report.handlebars`;
const STOCK_VALUE_REPORT_TEMPLATE = `${BASE_PATH}/stock_value.report.handlebars`;
const STOCK_EXPIRATION_REPORT_TEMPLATE = `${BASE_PATH}/stock_expiration_report.handlebars`;
const STOCK_AGGREGATED_CONSUMPTION_REPORT_TEMPLATE = `${BASE_PATH}/stock_aggregated_consumption_report.handlebars`;
const STOCK_AVG_MED_COSTS_PER_PATIENT_TEMPLATE = mkPath('/stock_avg_med_costs_per_patient.report');
const STOCK_EXIT_REPORT_TEMPLATE = mkPath('/stock_exit.report');
const STOCK_ENTRY_REPORT_TEMPLATE = mkPath('/stock_entry.report');
const STOCK_LOST_STOCK_REPORT_TEMPLATE = mkPath('/stock_lost_stock.report');
const STOCK_LOTS_REPORT_TEMPLATE = mkPath('/stock_lots.report');
const STOCK_MOVEMENTS_REPORT_TEMPLATE = mkPath('/stock_movements.report');
const STOCK_INLINE_MOVEMENTS_REPORT_TEMPLATE = mkPath('/stock_inline_movements.report');
const STOCK_INVENTORIES_REPORT_TEMPLATE = mkPath('/stock_inventories.report');
const STOCK_SHEET_REPORT_TEMPLATE = mkPath('/stock_sheet.report');
const STOCK_VALUE_REPORT_TEMPLATE = mkPath('/stock_value.report');
const STOCK_EXPIRATION_REPORT_TEMPLATE = mkPath('/stock_expiration_report');
const STOCK_AGGREGATED_CONSUMPTION_REPORT_TEMPLATE = mkPath('/stock_aggregated_consumption_report');

const ASSETS_REGISTRY_TEMPLATE = mkPath('/assets_registry.report');

// General imports
const _ = require('lodash');
Expand Down Expand Up @@ -242,4 +249,5 @@ module.exports = {
STOCK_EXPIRATION_REPORT_TEMPLATE,
STOCK_AGGREGATE_CONSUMPTION_TEMPLATE,
STOCK_AGGREGATED_CONSUMPTION_REPORT_TEMPLATE,
ASSETS_REGISTRY_TEMPLATE,
};
2 changes: 1 addition & 1 deletion server/controllers/stock/reports/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ exports.stockValueReporting = stockValue.reporting;

exports.stockAssignmentReceipt = stockAssignmentReceipt;
exports.stockRequisitionReceipt = stockRequisitionReceipt;
exports.purchaseOrderAnalysis = require('./purchase_order_analysis');

exports.purchaseOrderAnalysis = require('./purchase_order_analysis');
exports.purchasePrices = require('./purchase_prices');

exports.lotBarcodeReceipt = lotBarcodeReceipt;
Expand Down

0 comments on commit c2bd565

Please sign in to comment.