3 // Copyright (C) 2006-2021 Rod Roark <rod@sunsetsystems.com>
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")]);
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.
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];
68 $where = "WHERE 1 = 1";
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'";
84 $where .= " AND d.consumable != '1'";
88 $dion = $form_show_empty ?
"" : "AND di.on_hand != 0";
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 " .
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",
107 function generateEmptyTd($n)
111 $temp .= "<td></td>";
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']];
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']);
136 function mapToTable($row)
138 global $auth_admin, $auth_lots;
139 $today = date('Y-m-d');
141 echo " <tr class='detail'>\n";
142 $lastid = $row['drug_id'];
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";
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";
154 generate_display_field(array('data_type' => '1','list_id' => 'drug_form'), $row['form']) .
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']) .
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";
166 echo " <td title='" . xla('Not applicable') . "'> </td>\n";
169 if (!empty($row['inventory_id'][0])) {
171 foreach ($row['inventory_id'] as $key => $value) {
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]) .
181 echo " <div>" . text($row['lot_number'][$key]) . "</div>\n";
186 foreach ($row['facname'] as $value) {
187 $value = $value != null ?
$value : "N/A";
188 echo "<div >" . text($value) . "</div>";
192 foreach ($row['title'] as $value) {
193 $value = $value != null ?
$value : "N/A";
194 echo "<div >" . text($value) . "</div>";
198 foreach ($row['on_hand'] as $value) {
199 $value = $value != null ?
$value : "N/A";
200 echo "<div >" . text($value) . "</div>";
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>";
222 <title
><?php
echo xlt('Drug Inventory'); ?
></title
>
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 {
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
;
249 <?php Header
::setupHeader(['datatables', 'datatables-dt', 'datatables-bs', 'report-helper']); ?
>
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
;
279 theopts
[i
].selected
= false;
285 $
('#mymaintable').DataTable({
286 stripeClasses
:['stripe1','stripe2'],
288 <?php
// Bring in the translations ?>
289 <?php
require($GLOBALS['srcdir'] . '/js/xl/datatables-net.js.php'); ?
>
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%'>
302 <?php
echo xlt('Inventory Management'); ?
>
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)) {
316 echo " <option value='" . attr($facid) . "'";
317 if ($facid == $form_facility) {
320 echo ">" . text($frow['name']) . "\n";
324 // Build a drop-down list of warehouses.
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)) {
338 echo " <option value='" . attr("$whid/$facid") . "'";
339 echo " id='fac" . attr($facid) . "'";
340 if (strlen($form_warehouse) > 0 && $whid == $form_warehouse) {
343 echo ">" . text(xl_list_label($lrow['title'])) . "</option>\n";
348 <select name
='form_consumable'>
352 '0' => xl('All Product Types'),
353 '1' => xl('Consumable Only'),
354 '2' => xl('Non-Consumable Only'),
357 echo " <option value='" . attr($key) . "'";
358 if ($key == $form_consumable) {
361 echo ">" . text($value) . "</option>\n";
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'); ?
>
375 <input type
='submit' name
='form_refresh' value
="<?php echo xla('Refresh'); ?>" />
384 <!-- TODO
: Why are we not using the BS4 table
class here?
!-->
385 <table id
='mymaintable' class="table table-striped">
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
>
406 while ($row = sqlFetchArray($res)) {
407 if (!empty($row['inventory_id']) && $is_user_restricted && !isWarehouseAllowed($row['facid'], $row['warehouse_id'])) {
410 $row = processData($row);
411 if ($prevRow == '') {
415 if ($prevRow['drug_id'] == $row['drug_id']) {
416 $row = mergeData($prevRow, $row);
418 mapToTable($prevRow);
422 mapToTable($prevRow);
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) ?>" />