Improvements mostly concerning inventory.
[openemr.git] / interface / drugs / drug_inventory.php
blobb68ef0e80c6c80726ff100839bc23d78f7fb8b72
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\Core\Header;
17 // Check authorizations.
18 $auth_admin = AclMain::aclCheckCore('admin', 'drugs');
19 $auth_lots = $auth_admin ||
20 AclMain::aclCheckCore('inventory', 'lots') ||
21 AclMain::aclCheckCore('inventory', 'purchases') ||
22 AclMain::aclCheckCore('inventory', 'transfers') ||
23 AclMain::aclCheckCore('inventory', 'adjustments') ||
24 AclMain::aclCheckCore('inventory', 'consumption') ||
25 AclMain::aclCheckCore('inventory', 'destruction');
26 $auth_anything = $auth_lots ||
27 AclMain::aclCheckCore('inventory', 'sales') ||
28 AclMain::aclCheckCore('inventory', 'reporting');
29 if (!$auth_anything) {
30 die(xlt('Not authorized'));
32 // Note if user is restricted to any facilities and/or warehouses.
33 $is_user_restricted = isUserRestricted();
35 // For each sorting option, specify the ORDER BY argument.
37 $ORDERHASH = array(
38 'prod' => 'd.name, d.drug_id, di.expiration, di.lot_number',
39 'act' => 'd.active, d.name, d.drug_id, di.expiration, di.lot_number',
40 'ndc' => 'd.ndc_number, d.name, d.drug_id, di.expiration, di.lot_number',
41 'con' => 'd.consumable, d.name, d.drug_id, di.expiration, di.lot_number',
42 'form' => 'lof.title, d.name, d.drug_id, di.expiration, di.lot_number',
43 'lot' => 'di.lot_number, d.name, d.drug_id, di.expiration',
44 'wh' => 'lo.title, d.name, d.drug_id, di.expiration, di.lot_number',
45 'fac' => 'f.name, d.name, d.drug_id, di.expiration, di.lot_number',
46 'qoh' => 'di.on_hand, d.name, d.drug_id, di.expiration, di.lot_number',
47 'exp' => 'di.expiration, d.name, d.drug_id, di.lot_number',
50 $form_facility = 0 + empty($_REQUEST['form_facility']) ? 0 : $_REQUEST['form_facility'];
51 $form_show_empty = empty($_REQUEST['form_show_empty']) ? 0 : 1;
52 $form_show_inactive = empty($_REQUEST['form_show_inactive']) ? 0 : 1;
53 $form_consumable = isset($_REQUEST['form_consumable']) ? intval($_REQUEST['form_consumable']) : 0;
55 // Incoming form_warehouse, if not empty is in the form "warehouse/facility".
56 // The facility part is an attribute used by JavaScript logic.
57 $form_warehouse = empty($_REQUEST['form_warehouse']) ? '' : $_REQUEST['form_warehouse'];
58 $tmp = explode('/', $form_warehouse);
59 $form_warehouse = $tmp[0];
61 // Get the order hash array value and key for this request.
62 $form_orderby = isset($ORDERHASH[$_REQUEST['form_orderby'] ?? '']) ? $_REQUEST['form_orderby'] : 'prod';
63 $orderby = $ORDERHASH[$form_orderby];
65 $binds = array();
66 $where = "WHERE 1 = 1";
67 if ($form_facility) {
68 $where .= " AND lo.option_value IS NOT NULL AND lo.option_value = ?";
69 $binds[] = $form_facility;
71 if ($form_warehouse) {
72 $where .= " AND di.warehouse_id IS NOT NULL AND di.warehouse_id = ?";
73 $binds[] = $form_warehouse;
75 if (!$form_show_inactive) {
76 $where .= " AND d.active = 1";
78 if ($form_consumable) {
79 if ($form_consumable == 1) {
80 $where .= " AND d.consumable = '1'";
81 } else {
82 $where .= " AND d.consumable != '1'";
86 $dion = $form_show_empty ? "" : "AND di.on_hand != 0";
88 // get drugs
89 $res = sqlStatement(
90 "SELECT d.*, " .
91 "di.inventory_id, di.lot_number, di.expiration, di.manufacturer, di.on_hand, " .
92 "di.warehouse_id, lo.title, lo.option_value AS facid, f.name AS facname " .
93 "FROM drugs AS d " .
94 "LEFT JOIN drug_inventory AS di ON di.drug_id = d.drug_id " .
95 "AND di.destroy_date IS NULL $dion " .
96 "LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " .
97 "lo.option_id = di.warehouse_id AND lo.activity = 1 " .
98 "LEFT JOIN facility AS f ON f.id = lo.option_value " .
99 "LEFT JOIN list_options AS lof ON lof.list_id = 'drug_form' AND " .
100 "lof.option_id = d.form AND lof.activity = 1 " .
101 "$where ORDER BY d.active DESC, $orderby",
102 $binds
105 function generateEmptyTd($n)
107 $temp = '';
108 while ($n > 0) {
109 $temp .= "<td></td>";
110 $n--;
112 echo $temp;
114 function processData($data)
116 $data['inventory_id'] = [$data['inventory_id']];
117 $data['lot_number'] = [$data['lot_number']];
118 $data['facname'] = [$data['facname']];
119 $data['title'] = [$data['title']];
120 $data['on_hand'] = [$data['on_hand']];
121 $data['expiration'] = [$data['expiration']];
122 return $data;
124 function mergeData($d1, $d2)
126 $d1['inventory_id'] = array_merge($d1['inventory_id'], $d2['inventory_id']);
127 $d1['lot_number'] = array_merge($d1['lot_number'], $d2['lot_number']);
128 $d1['facname'] = array_merge($d1['facname'], $d2['facname']);
129 $d1['title'] = array_merge($d1['title'], $d2['title']);
130 $d1['on_hand'] = array_merge($d1['on_hand'], $d2['on_hand']);
131 $d1['expiration'] = array_merge($d1['expiration'], $d2['expiration']);
132 return $d1;
134 function mapToTable($row)
136 global $auth_admin, $auth_lots;
137 $today = date('Y-m-d');
138 if ($row) {
139 echo " <tr class='detail'>\n";
140 $lastid = $row['drug_id'];
141 if ($auth_admin) {
142 echo "<td title='" . xla('Click to edit') . "' onclick='dodclick(" . attr(addslashes($lastid)) . ")'>" .
143 "<a href='' onclick='return false'>" .
144 text($row['name']) . "</a></td>\n";
145 } else {
146 echo " <td>" . text($row['name']) . "</td>\n";
148 echo " <td>" . ($row['active'] ? xlt('Yes') : xlt('No')) . "</td>\n";
149 echo " <td>" . ($row['consumable'] ? xlt('Yes') : xlt('No')) . "</td>\n";
150 echo " <td>" . text($row['ndc_number']) . "</td>\n";
151 echo " <td>" .
152 generate_display_field(array('data_type' => '1','list_id' => 'drug_form'), $row['form']) .
153 "</td>\n";
154 echo " <td>" . text($row['size']) . "</td>\n";
155 echo " <td title='" . xla('Measurement Units') . "'>" .
156 generate_display_field(array('data_type' => '1','list_id' => 'drug_units'), $row['unit']) .
157 "</td>\n";
159 if ($auth_lots && $row['dispensable']) {
160 echo " <td onclick='doiclick(" . intval($lastid) . ",0)' title='" .
161 xla('Purchase or Transfer') . "' style='padding:0'>" .
162 "<input type='button' value='" . xla('Tran') . "'style='padding:0' /></td>\n";
163 } else {
164 echo " <td title='" . xla('Not applicable') . "'>&nbsp;</td>\n";
167 if (!empty($row['inventory_id'][0])) {
168 echo "<td>";
169 foreach ($row['inventory_id'] as $key => $value) {
170 if ($auth_lots) {
171 echo "<div title='" .
172 xla('Adjustment, Consumption, Return, or Edit') .
173 "' onclick='doiclick(" . intval($lastid) . "," .
174 intval($row['inventory_id'][$key]) . ")'>" .
175 "<a href='' onclick='return false'>" .
176 text($row['lot_number'][$key]) .
177 "</a></div>";
178 } else {
179 echo " <div>" . text($row['lot_number'][$key]) . "</div>\n";
182 echo "</td>\n<td>";
184 foreach ($row['facname'] as $value) {
185 $value = $value != null ? $value : "N/A";
186 echo "<div >" . text($value) . "</div>";
188 echo "</td>\n<td>";
190 foreach ($row['title'] as $value) {
191 $value = $value != null ? $value : "N/A";
192 echo "<div >" . text($value) . "</div>";
194 echo "</td>\n<td>";
196 foreach ($row['on_hand'] as $value) {
197 $value = $value != null ? $value : "N/A";
198 echo "<div >" . text($value) . "</div>";
200 echo "</td>\n<td>";
202 foreach ($row['expiration'] as $value) {
203 // Make the expiration date red if expired.
204 $expired = !empty($value) && strcmp($value, $today) <= 0;
205 $value = !empty($value) ? oeFormatShortDate($value) : xl('N/A');
206 echo "<div" . ($expired ? " style='color:red'" : "") . ">" . text($value) . "</div>";
208 echo "</td>\n";
209 } else {
210 generateEmptyTd(4);
212 echo " </tr>\n";
216 <html>
218 <head>
220 <title><?php echo xlt('Drug Inventory'); ?></title>
222 <style>
223 a, a:visited, a:hover {
224 color: var(--primary);
226 #mymaintable thead .sorting::before,
227 #mymaintable thead .sorting_asc::before,
228 #mymaintable thead .sorting_asc::after,
229 #mymaintable thead .sorting_desc::before,
230 #mymaintable thead .sorting_desc::after,
231 #mymaintable thead .sorting::after {
232 display: none;
235 .dataTables_wrapper .dataTables_paginate .paginate_button {
236 padding: 0 !important;
237 margin: 0 !important;
238 border: 0 !important;
241 .paginate_button:hover {
242 background: transparent !important;
245 </style>
247 <?php Header::setupHeader(['datatables', 'datatables-dt', 'datatables-bs', 'report-helper']); ?>
249 <script>
251 // callback from add_edit_drug.php or add_edit_drug_inventory.php:
252 function refreshme() {
253 // Avoiding reload() here because it generates a browser warning about repeating a POST.
254 location.href = location.href;
257 // Process click on drug title.
258 function dodclick(id) {
259 dlgopen('add_edit_drug.php?drug=' + id, '_blank', 900, 600);
262 // Process click on drug QOO or lot.
263 function doiclick(id, lot) {
264 dlgopen('add_edit_lot.php?drug=' + id + '&lot=' + lot, '_blank', 600, 475);
267 // Enable/disable warehouse options depending on current facility.
268 function facchanged() {
269 var f = document.forms[0];
270 var facid = f.form_facility.value;
271 var theopts = f.form_warehouse.options;
272 for (var i = 1; i < theopts.length; ++i) {
273 var tmp = theopts[i].value.split('/');
274 var dis = facid && (tmp.length < 2 || tmp[1] != facid);
275 theopts[i].disabled = dis;
276 if (dis) {
277 theopts[i].selected = false;
282 $(function () {
283 $('#mymaintable').DataTable({
284 stripeClasses:['stripe1','stripe2'],
285 orderClasses: false,
286 <?php // Bring in the translations ?>
287 <?php require($GLOBALS['srcdir'] . '/js/xl/datatables-net.js.php'); ?>
290 </script>
292 </head>
294 <body class="body_top">
295 <form method='post' action='drug_inventory.php' onsubmit='return top.restoreSession()'>
297 <table border='0' cellpadding='3' width='100%'>
298 <tr>
299 <td>
300 <b><?php echo xlt('Inventory Management'); ?></b>
301 </td>
302 <td align='right'>
303 <?php
304 // Build a drop-down list of facilities.
305 $query = "SELECT id, name FROM facility ORDER BY name";
306 $fres = sqlStatement($query);
307 echo " <select name='form_facility' onchange='facchanged()'>\n";
308 echo " <option value=''>-- " . xlt('All Facilities') . " --\n";
309 while ($frow = sqlFetchArray($fres)) {
310 $facid = $frow['id'];
311 if ($is_user_restricted && !isFacilityAllowed($facid)) {
312 continue;
314 echo " <option value='" . attr($facid) . "'";
315 if ($facid == $form_facility) {
316 echo " selected";
318 echo ">" . text($frow['name']) . "\n";
320 echo " </select>\n";
322 // Build a drop-down list of warehouses.
323 echo "&nbsp;";
324 echo " <select name='form_warehouse'>\n";
325 echo " <option value=''>" . xlt('All Warehouses') . "</option>\n";
326 $lres = sqlStatement(
327 "SELECT * FROM list_options " .
328 "WHERE list_id = 'warehouse' ORDER BY seq, title"
330 while ($lrow = sqlFetchArray($lres)) {
331 $whid = $lrow['option_id'];
332 $facid = $lrow['option_value'];
333 if ($is_user_restricted && !isWarehouseAllowed($facid, $whid)) {
334 continue;
336 echo " <option value='" . attr("$whid/$facid") . "'";
337 echo " id='fac" . attr($facid) . "'";
338 if (strlen($form_warehouse) > 0 && $whid == $form_warehouse) {
339 echo " selected";
341 echo ">" . text(xl_list_label($lrow['title'])) . "</option>\n";
343 echo " </select>\n";
345 &nbsp;
346 <select name='form_consumable'>
347 <?php
348 foreach (
349 array(
350 '0' => xl('All Product Types'),
351 '1' => xl('Consumable Only'),
352 '2' => xl('Non-Consumable Only'),
353 ) as $key => $value
355 echo " <option value='" . attr($key) . "'";
356 if ($key == $form_consumable) {
357 echo " selected";
359 echo ">" . text($value) . "</option>\n";
362 </select>&nbsp;
363 </td>
364 <td>
365 <input type='checkbox' name='form_show_empty' value='1'<?php if ($form_show_empty) {
366 echo " checked";} ?> />
367 <?php echo xl('Show empty lots'); ?><br />
368 <input type='checkbox' name='form_show_inactive' value='1'<?php if ($form_show_inactive) {
369 echo " checked";} ?> />
370 <?php echo xl('Show inactive'); ?>
371 </td>
372 <td>
373 <input type='submit' name='form_refresh' value="<?php echo xla('Refresh'); ?>" />
374 </td>
375 </tr>
376 <tr>
377 <td height="1">
378 </td>
379 </tr>
380 </table>
382 <!-- TODO: Why are we not using the BS4 table class here? !-->
383 <table id='mymaintable' class="display table-striped">
384 <thead>
385 <tr class='head'>
386 <th>
387 <?php echo xlt('Name'); ?> </a>
388 </th>
389 <th>
390 <?php echo xlt('Act'); ?>
391 </th>
392 <th>
393 <?php echo xlt('Cons'); ?>
394 </th>
395 <th>
396 <?php echo xlt('NDC'); ?> </a>
397 </th>
398 <th>
399 <?php echo xlt('Form'); ?> </a>
400 </th>
401 <th>
402 <?php echo xlt('Size'); ?>
403 </th>
404 <th title='<?php echo xlt('Measurement Units'); ?>'>
405 <?php echo xlt('Unit'); ?>
406 </th>
407 <th title='<?php echo xla('Purchase or Transfer'); ?>'>
408 <?php echo xlt('Tran'); ?>
409 </th>
410 <th>
411 <?php echo xlt('Lot'); ?> </a>
412 </th>
413 <th>
414 <?php echo xlt('Facility'); ?> </a>
415 </th>
416 <th>
417 <?php echo xlt('Warehouse'); ?> </a>
418 </th>
419 <th>
420 <?php echo xlt('QOH'); ?> </a>
421 </th>
422 <th>
423 <?php echo xlt('Expires'); ?> </a>
424 </th>
425 </tr>
426 </thead>
427 <tbody>
428 <?php
429 $prevRow = '';
430 while ($row = sqlFetchArray($res)) {
431 if (!empty($row['inventory_id']) && $is_user_restricted && !isWarehouseAllowed($row['facid'], $row['warehouse_id'])) {
432 continue;
434 $row = processData($row);
435 if ($prevRow == '') {
436 $prevRow = $row;
437 continue;
439 if ($prevRow['drug_id'] == $row['drug_id']) {
440 $row = mergeData($prevRow, $row);
441 } else {
442 mapToTable($prevRow);
444 $prevRow = $row;
445 } // end while
446 mapToTable($prevRow);
448 </tbody>
449 </table>
451 <input class="btn btn-primary btn-block w-25 mx-auto" type='button' value='<?php echo xla('Add Drug'); ?>' onclick='dodclick(0)' />
453 <input type="hidden" name="form_orderby" value="<?php echo attr($form_orderby) ?>" />
455 </form>
457 <script>
458 facchanged();
459 </script>
461 </body>
462 </html>