fix: bug fix (#6270)
[openemr.git] / interface / drugs / drug_inventory.php
bloba40bd7f6375d6338c6955a6ba3aea8a230b86b9d
1 <?php
3 // Copyright (C) 2006-2021 Rod Roark <rod@sunsetsystems.com>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 require_once("../globals.php");
11 require_once("drugs.inc.php");
12 require_once("$srcdir/options.inc.php");
14 use OpenEMR\Common\Acl\AclMain;
15 use OpenEMR\Common\Twig\TwigContainer;
16 use OpenEMR\Core\Header;
18 // Check authorizations.
19 $auth_admin = AclMain::aclCheckCore('admin', 'drugs');
20 $auth_lots = $auth_admin ||
21 AclMain::aclCheckCore('inventory', 'lots') ||
22 AclMain::aclCheckCore('inventory', 'purchases') ||
23 AclMain::aclCheckCore('inventory', 'transfers') ||
24 AclMain::aclCheckCore('inventory', 'adjustments') ||
25 AclMain::aclCheckCore('inventory', 'consumption') ||
26 AclMain::aclCheckCore('inventory', 'destruction');
27 $auth_anything = $auth_lots ||
28 AclMain::aclCheckCore('inventory', 'sales') ||
29 AclMain::aclCheckCore('inventory', 'reporting');
30 if (!$auth_anything) {
31 echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Drug Inventory")]);
32 exit;
34 // Note if user is restricted to any facilities and/or warehouses.
35 $is_user_restricted = isUserRestricted();
37 // For each sorting option, specify the ORDER BY argument.
39 $ORDERHASH = array(
40 'prod' => 'd.name, d.drug_id, di.expiration, di.lot_number',
41 'act' => 'd.active, d.name, d.drug_id, di.expiration, di.lot_number',
42 'ndc' => 'd.ndc_number, d.name, d.drug_id, di.expiration, di.lot_number',
43 'con' => 'd.consumable, d.name, d.drug_id, di.expiration, di.lot_number',
44 'form' => 'lof.title, d.name, d.drug_id, di.expiration, di.lot_number',
45 'lot' => 'di.lot_number, d.name, d.drug_id, di.expiration',
46 'wh' => 'lo.title, d.name, d.drug_id, di.expiration, di.lot_number',
47 'fac' => 'f.name, d.name, d.drug_id, di.expiration, di.lot_number',
48 'qoh' => 'di.on_hand, d.name, d.drug_id, di.expiration, di.lot_number',
49 'exp' => 'di.expiration, d.name, d.drug_id, di.lot_number',
52 $form_facility = 0 + empty($_REQUEST['form_facility']) ? 0 : $_REQUEST['form_facility'];
53 $form_show_empty = empty($_REQUEST['form_show_empty']) ? 0 : 1;
54 $form_show_inactive = empty($_REQUEST['form_show_inactive']) ? 0 : 1;
55 $form_consumable = isset($_REQUEST['form_consumable']) ? intval($_REQUEST['form_consumable']) : 0;
57 // Incoming form_warehouse, if not empty is in the form "warehouse/facility".
58 // The facility part is an attribute used by JavaScript logic.
59 $form_warehouse = empty($_REQUEST['form_warehouse']) ? '' : $_REQUEST['form_warehouse'];
60 $tmp = explode('/', $form_warehouse);
61 $form_warehouse = $tmp[0];
63 // Get the order hash array value and key for this request.
64 $form_orderby = isset($ORDERHASH[$_REQUEST['form_orderby'] ?? '']) ? $_REQUEST['form_orderby'] : 'prod';
65 $orderby = $ORDERHASH[$form_orderby];
67 $binds = array();
68 $where = "WHERE 1 = 1";
69 if ($form_facility) {
70 $where .= " AND lo.option_value IS NOT NULL AND lo.option_value = ?";
71 $binds[] = $form_facility;
73 if ($form_warehouse) {
74 $where .= " AND di.warehouse_id IS NOT NULL AND di.warehouse_id = ?";
75 $binds[] = $form_warehouse;
77 if (!$form_show_inactive) {
78 $where .= " AND d.active = 1";
80 if ($form_consumable) {
81 if ($form_consumable == 1) {
82 $where .= " AND d.consumable = '1'";
83 } else {
84 $where .= " AND d.consumable != '1'";
88 $dion = $form_show_empty ? "" : "AND di.on_hand != 0";
90 // get drugs
91 $res = sqlStatement(
92 "SELECT d.*, " .
93 "di.inventory_id, di.lot_number, di.expiration, di.manufacturer, di.on_hand, " .
94 "di.warehouse_id, lo.title, lo.option_value AS facid, f.name AS facname " .
95 "FROM drugs AS d " .
96 "LEFT JOIN drug_inventory AS di ON di.drug_id = d.drug_id " .
97 "AND di.destroy_date IS NULL $dion " .
98 "LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " .
99 "lo.option_id = di.warehouse_id AND lo.activity = 1 " .
100 "LEFT JOIN facility AS f ON f.id = lo.option_value " .
101 "LEFT JOIN list_options AS lof ON lof.list_id = 'drug_form' AND " .
102 "lof.option_id = d.form AND lof.activity = 1 " .
103 "$where ORDER BY d.active DESC, $orderby",
104 $binds
107 function generateEmptyTd($n)
109 $temp = '';
110 while ($n > 0) {
111 $temp .= "<td></td>";
112 $n--;
114 echo $temp;
116 function processData($data)
118 $data['inventory_id'] = [$data['inventory_id']];
119 $data['lot_number'] = [$data['lot_number']];
120 $data['facname'] = [$data['facname']];
121 $data['title'] = [$data['title']];
122 $data['on_hand'] = [$data['on_hand']];
123 $data['expiration'] = [$data['expiration']];
124 return $data;
126 function mergeData($d1, $d2)
128 $d1['inventory_id'] = array_merge($d1['inventory_id'], $d2['inventory_id']);
129 $d1['lot_number'] = array_merge($d1['lot_number'], $d2['lot_number']);
130 $d1['facname'] = array_merge($d1['facname'], $d2['facname']);
131 $d1['title'] = array_merge($d1['title'], $d2['title']);
132 $d1['on_hand'] = array_merge($d1['on_hand'], $d2['on_hand']);
133 $d1['expiration'] = array_merge($d1['expiration'], $d2['expiration']);
134 return $d1;
136 function mapToTable($row)
138 global $auth_admin, $auth_lots;
139 $today = date('Y-m-d');
140 if ($row) {
141 echo " <tr class='detail'>\n";
142 $lastid = $row['drug_id'];
143 if ($auth_admin) {
144 echo "<td title='" . xla('Click to edit') . "' onclick='dodclick(" . attr(addslashes($lastid)) . ")'>" .
145 "<a href='' onclick='return false'>" .
146 text($row['name']) . "</a></td>\n";
147 } else {
148 echo " <td>" . text($row['name']) . "</td>\n";
150 echo " <td>" . ($row['active'] ? xlt('Yes') : xlt('No')) . "</td>\n";
151 echo " <td>" . ($row['consumable'] ? xlt('Yes') : xlt('No')) . "</td>\n";
152 echo " <td>" . text($row['ndc_number']) . "</td>\n";
153 echo " <td>" .
154 generate_display_field(array('data_type' => '1','list_id' => 'drug_form'), $row['form']) .
155 "</td>\n";
156 echo " <td>" . text($row['size']) . "</td>\n";
157 echo " <td title='" . xla('Measurement Units') . "'>" .
158 generate_display_field(array('data_type' => '1','list_id' => 'drug_units'), $row['unit']) .
159 "</td>\n";
161 if ($auth_lots && $row['dispensable']) {
162 echo " <td onclick='doiclick(" . intval($lastid) . ",0)' title='" .
163 xla('Purchase or Transfer') . "' style='padding:0'>" .
164 "<input type='button' value='" . xla('Tran') . "'style='padding:0' /></td>\n";
165 } else {
166 echo " <td title='" . xla('Not applicable') . "'>&nbsp;</td>\n";
169 if (!empty($row['inventory_id'][0])) {
170 echo "<td>";
171 foreach ($row['inventory_id'] as $key => $value) {
172 if ($auth_lots) {
173 echo "<div title='" .
174 xla('Adjustment, Consumption, Return, or Edit') .
175 "' onclick='doiclick(" . intval($lastid) . "," .
176 intval($row['inventory_id'][$key]) . ")'>" .
177 "<a href='' onclick='return false'>" .
178 text($row['lot_number'][$key]) .
179 "</a></div>";
180 } else {
181 echo " <div>" . text($row['lot_number'][$key]) . "</div>\n";
184 echo "</td>\n<td>";
186 foreach ($row['facname'] as $value) {
187 $value = $value != null ? $value : "N/A";
188 echo "<div >" . text($value) . "</div>";
190 echo "</td>\n<td>";
192 foreach ($row['title'] as $value) {
193 $value = $value != null ? $value : "N/A";
194 echo "<div >" . text($value) . "</div>";
196 echo "</td>\n<td>";
198 foreach ($row['on_hand'] as $value) {
199 $value = $value != null ? $value : "N/A";
200 echo "<div >" . text($value) . "</div>";
202 echo "</td>\n<td>";
204 foreach ($row['expiration'] as $value) {
205 // Make the expiration date red if expired.
206 $expired = !empty($value) && strcmp($value, $today) <= 0;
207 $value = !empty($value) ? oeFormatShortDate($value) : xl('N/A');
208 echo "<div" . ($expired ? " style='color:red'" : "") . ">" . text($value) . "</div>";
210 echo "</td>\n";
211 } else {
212 generateEmptyTd(4);
214 echo " </tr>\n";
218 <html>
220 <head>
222 <title><?php echo xlt('Drug Inventory'); ?></title>
224 <style>
225 a, a:visited, a:hover {
226 color: var(--primary);
228 #mymaintable thead .sorting::before,
229 #mymaintable thead .sorting_asc::before,
230 #mymaintable thead .sorting_asc::after,
231 #mymaintable thead .sorting_desc::before,
232 #mymaintable thead .sorting_desc::after,
233 #mymaintable thead .sorting::after {
234 display: none;
237 .dataTables_wrapper .dataTables_paginate .paginate_button {
238 padding: 0 !important;
239 margin: 0 !important;
240 border: 0 !important;
243 .paginate_button:hover {
244 background: transparent !important;
247 </style>
249 <?php Header::setupHeader(['datatables', 'datatables-dt', 'datatables-bs', 'report-helper']); ?>
251 <script>
253 // callback from add_edit_drug.php or add_edit_drug_inventory.php:
254 function refreshme() {
255 // Avoiding reload() here because it generates a browser warning about repeating a POST.
256 location.href = location.href;
259 // Process click on drug title.
260 function dodclick(id) {
261 dlgopen('add_edit_drug.php?drug=' + id, '_blank', 900, 600);
264 // Process click on drug QOO or lot.
265 function doiclick(id, lot) {
266 dlgopen('add_edit_lot.php?drug=' + id + '&lot=' + lot, '_blank', 600, 475);
269 // Enable/disable warehouse options depending on current facility.
270 function facchanged() {
271 var f = document.forms[0];
272 var facid = f.form_facility.value;
273 var theopts = f.form_warehouse.options;
274 for (var i = 1; i < theopts.length; ++i) {
275 var tmp = theopts[i].value.split('/');
276 var dis = facid && (tmp.length < 2 || tmp[1] != facid);
277 theopts[i].disabled = dis;
278 if (dis) {
279 theopts[i].selected = false;
284 $(function () {
285 $('#mymaintable').DataTable({
286 stripeClasses:['stripe1','stripe2'],
287 orderClasses: false,
288 <?php // Bring in the translations ?>
289 <?php require($GLOBALS['srcdir'] . '/js/xl/datatables-net.js.php'); ?>
292 </script>
294 </head>
296 <body class="body_top">
297 <form method='post' action='drug_inventory.php' onsubmit='return top.restoreSession()'>
299 <table border='0' cellpadding='3' width='100%'>
300 <tr>
301 <td>
302 <?php echo xlt('Inventory Management'); ?>
303 </td>
304 <td align='right'>
305 <?php
306 // Build a drop-down list of facilities.
307 $query = "SELECT id, name FROM facility ORDER BY name";
308 $fres = sqlStatement($query);
309 echo " <select name='form_facility' onchange='facchanged()'>\n";
310 echo " <option value=''>-- " . xlt('All Facilities') . " --\n";
311 while ($frow = sqlFetchArray($fres)) {
312 $facid = $frow['id'];
313 if ($is_user_restricted && !isFacilityAllowed($facid)) {
314 continue;
316 echo " <option value='" . attr($facid) . "'";
317 if ($facid == $form_facility) {
318 echo " selected";
320 echo ">" . text($frow['name']) . "\n";
322 echo " </select>\n";
324 // Build a drop-down list of warehouses.
325 echo "&nbsp;";
326 echo " <select name='form_warehouse'>\n";
327 echo " <option value=''>" . xlt('All Warehouses') . "</option>\n";
328 $lres = sqlStatement(
329 "SELECT * FROM list_options " .
330 "WHERE list_id = 'warehouse' ORDER BY seq, title"
332 while ($lrow = sqlFetchArray($lres)) {
333 $whid = $lrow['option_id'];
334 $facid = $lrow['option_value'];
335 if ($is_user_restricted && !isWarehouseAllowed($facid, $whid)) {
336 continue;
338 echo " <option value='" . attr("$whid/$facid") . "'";
339 echo " id='fac" . attr($facid) . "'";
340 if (strlen($form_warehouse) > 0 && $whid == $form_warehouse) {
341 echo " selected";
343 echo ">" . text(xl_list_label($lrow['title'])) . "</option>\n";
345 echo " </select>\n";
347 &nbsp;
348 <select name='form_consumable'>
349 <?php
350 foreach (
351 array(
352 '0' => xl('All Product Types'),
353 '1' => xl('Consumable Only'),
354 '2' => xl('Non-Consumable Only'),
355 ) as $key => $value
357 echo " <option value='" . attr($key) . "'";
358 if ($key == $form_consumable) {
359 echo " selected";
361 echo ">" . text($value) . "</option>\n";
364 </select>&nbsp;
365 </td>
366 <td>
367 <input type='checkbox' name='form_show_empty' value='1'<?php if ($form_show_empty) {
368 echo " checked";} ?> />
369 <?php echo xlt('Show empty lots'); ?><br />
370 <input type='checkbox' name='form_show_inactive' value='1'<?php if ($form_show_inactive) {
371 echo " checked";} ?> />
372 <?php echo xlt('Show inactive'); ?>
373 </td>
374 <td>
375 <input type='submit' name='form_refresh' value="<?php echo xla('Refresh'); ?>" />
376 </td>
377 </tr>
378 <tr>
379 <td height="1">
380 </td>
381 </tr>
382 </table>
384 <!-- TODO: Why are we not using the BS4 table class here? !-->
385 <table id='mymaintable' class="table table-striped">
386 <thead>
387 <tr>
388 <th><?php echo xlt('Name'); ?> </a></th>
389 <th><?php echo xlt('Act'); ?></th>
390 <th><?php echo xlt('Cons'); ?></th>
391 <th><?php echo xlt('NDC'); ?> </a></th>
392 <th><?php echo xlt('Form'); ?> </a></th>
393 <th><?php echo xlt('Size'); ?></th>
394 <th title='<?php echo xlt('Measurement Units'); ?>'><?php echo xlt('Unit'); ?></th>
395 <th title='<?php echo xla('Purchase or Transfer'); ?>'><?php echo xlt('Tran'); ?></th>
396 <th><?php echo xlt('Lot'); ?> </a></th>
397 <th><?php echo xlt('Facility'); ?> </a></th>
398 <th><?php echo xlt('Warehouse'); ?> </a></th>
399 <th><?php echo xlt('QOH'); ?> </a></th>
400 <th><?php echo xlt('Expires'); ?> </a></th>
401 </tr>
402 </thead>
403 <tbody>
404 <?php
405 $prevRow = '';
406 while ($row = sqlFetchArray($res)) {
407 if (!empty($row['inventory_id']) && $is_user_restricted && !isWarehouseAllowed($row['facid'], $row['warehouse_id'])) {
408 continue;
410 $row = processData($row);
411 if ($prevRow == '') {
412 $prevRow = $row;
413 continue;
415 if ($prevRow['drug_id'] == $row['drug_id']) {
416 $row = mergeData($prevRow, $row);
417 } else {
418 mapToTable($prevRow);
420 $prevRow = $row;
421 } // end while
422 mapToTable($prevRow);
424 </tbody>
425 </table>
427 <input class="btn btn-primary btn-block w-25 mx-auto" type='button' value='<?php echo xla('Add Drug'); ?>' onclick='dodclick(0)' />
429 <input type="hidden" name="form_orderby" value="<?php echo attr($form_orderby) ?>" />
431 </form>
433 <script>
434 facchanged();
435 </script>
437 </body>
438 </html>