You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
969 lines
36 KiB
969 lines
36 KiB
<?php |
|
|
|
/** |
|
* inventory_list.php |
|
* |
|
* @package OpenEMR |
|
* @link http://www.open-emr.org |
|
* @author Rod Roark <rod@sunsetsystems.com> |
|
* @author Brady Miller <brady.g.miller@gmail.com> |
|
* @copyright Copyright (c) 2008-2021 Rod Roark <rod@sunsetsystems.com> |
|
* @copyright Copyright (c) 2018-2019 Brady Miller <brady.g.miller@gmail.com> |
|
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 |
|
*/ |
|
|
|
require_once("../globals.php"); |
|
require_once("$srcdir/options.inc.php"); |
|
require_once("$include_root/drugs/drugs.inc.php"); |
|
|
|
use OpenEMR\Common\Acl\AclMain; |
|
use OpenEMR\Common\Csrf\CsrfUtils; |
|
use OpenEMR\Common\Twig\TwigContainer; |
|
use OpenEMR\Core\Header; |
|
|
|
if (!empty($_POST)) { |
|
if (!CsrfUtils::verifyCsrfToken($_POST["csrf_token_form"])) { |
|
CsrfUtils::csrfNotVerified(); |
|
} |
|
} |
|
|
|
// For each sorting option, specify the ORDER BY argument. |
|
// |
|
$ORDERHASH = array( |
|
'name' => 'd.name, d.drug_id', |
|
'act' => 'd.active, d.name, d.drug_id', |
|
'con' => 'd.consumable, d.name, d.drug_id', |
|
); |
|
|
|
// Check permission for this report. |
|
$auth_drug_reports = $GLOBALS['inhouse_pharmacy'] && ( |
|
AclMain::aclCheckCore('admin', 'drugs') || |
|
AclMain::aclCheckCore('inventory', 'reporting')); |
|
if (!$auth_drug_reports) { |
|
echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Inventory List")]); |
|
exit; |
|
} |
|
|
|
// Note if user is restricted to any facilities and/or warehouses. |
|
$is_user_restricted = isUserRestricted(); |
|
|
|
function addWarning($msg) |
|
{ |
|
global $warnings, $form_action; |
|
$break = $form_action != 'export' ? '<br />' : '; '; |
|
if ($warnings) { |
|
$warnings .= $break; |
|
} |
|
$warnings .= text($msg); |
|
} |
|
|
|
// Check if a product needs to be re-ordered, optionally for a given warehouse. |
|
// |
|
function checkReorder($drug_id, $min, $warehouse = '') |
|
{ |
|
global $form_days; |
|
|
|
if (!$min) { |
|
return false; |
|
} |
|
|
|
$query = "SELECT " . |
|
"SUM(s.quantity) AS sale_quantity " . |
|
"FROM drug_sales AS s " . |
|
"LEFT JOIN drug_inventory AS di ON di.inventory_id = s.inventory_id " . |
|
"WHERE " . |
|
"s.drug_id = ? AND " . |
|
"s.sale_date > DATE_SUB(NOW(), INTERVAL ? DAY) " . |
|
"AND s.pid != 0"; |
|
$binds = array($drug_id, $form_days); |
|
if ($warehouse !== '') { |
|
$query .= " AND di.warehouse_id = ?"; |
|
$binds[] = $warehouse; |
|
} |
|
$srow = sqlQuery($query, $binds); |
|
$sales = 0 + $srow['sale_quantity']; |
|
|
|
$query = "SELECT SUM(on_hand) AS on_hand " . |
|
"FROM drug_inventory AS di WHERE " . |
|
"di.drug_id = ? AND " . |
|
"(di.expiration IS NULL OR di.expiration > NOW()) AND " . |
|
"di.destroy_date IS NULL"; |
|
$binds = array($drug_id); |
|
if ($warehouse !== '') { |
|
$query .= " AND di.warehouse_id = ?"; |
|
$binds[] = $warehouse; |
|
} |
|
$ohrow = sqlQuery($query, $binds); |
|
$onhand = intval($ohrow['on_hand']); |
|
|
|
if (empty($GLOBALS['gbl_min_max_months'])) { |
|
if ($onhand <= $min) { |
|
return true; |
|
} |
|
} else { |
|
if ($sales != 0) { |
|
$stock_months = sprintf('%0.1f', $onhand * ($form_days / 30.41) / $sales); |
|
if ($stock_months <= $min) { |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// Generate the list of warehouse IDs that the current user is allowed to access. |
|
// This is used to build SQL for $uwcond. |
|
// |
|
function genUserWarehouses($userid = 0) |
|
{ |
|
$list = ''; |
|
$res = sqlStatement( |
|
"SELECT DISTINCT option_id, option_value FROM list_options WHERE " . |
|
"list_id = 'warehouse' AND activity = 1" |
|
); |
|
while ($row = sqlFetchArray($res)) { |
|
if (isWarehouseAllowed($row['option_value'], $row['option_id'], $userid)) { |
|
if ($list != '') { |
|
$list .= ', '; |
|
} |
|
// Using add_escape_custom because this string is for a SQL query. |
|
$list .= "'" . add_escape_custom($row['option_id']) . "'"; |
|
} |
|
} |
|
return $list; |
|
} |
|
|
|
// This counts the number of days that have a starting zero inventory for a given product |
|
// since a given start date with given restrictions for warehouse or facility. |
|
// End date is assumed to be the current date. |
|
// |
|
// function zeroDays($product_id, $begdate, $warehouse_id = '~', $facility_id = 0) { |
|
function zeroDays($product_id, $begdate, $extracond, $extrabind, $min_sale = 1) |
|
{ |
|
$today = date('Y-m-d'); |
|
|
|
$prodcond = ''; |
|
$prodbind = array(); |
|
if ($product_id) { |
|
$prodcond = "AND di.drug_id = ?"; |
|
$prodbind[] = $product_id; |
|
} |
|
|
|
// This will be an array where key is date and value is quantity. |
|
// For each date key the value represents net quantity changes for that day. |
|
$qtys = array(); |
|
|
|
// Force it to have entries for the begin and end dates. |
|
$qtys[$today] = 0; |
|
$qtys[$begdate] = 0; |
|
|
|
// Get sums of current inventory quantities. |
|
$query = "SELECT SUM(di.on_hand) AS on_hand " . |
|
"FROM drug_inventory AS di " . |
|
"LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " . |
|
"lo.option_id = di.warehouse_id AND lo.activity = 1 " . |
|
"WHERE " . |
|
"di.destroy_date IS NULL $prodcond $extracond"; |
|
$row = sqlQuery($query, array_merge($prodbind, $extrabind)); |
|
$current_qoh = $row['on_hand']; |
|
|
|
// Add sums of destructions done for each date (effectively a type of transaction). |
|
$res = sqlStatement( |
|
"SELECT di.destroy_date, SUM(di.on_hand) AS on_hand " . |
|
"FROM drug_inventory AS di " . |
|
"LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " . |
|
"lo.option_id = di.warehouse_id AND lo.activity = 1 " . |
|
"WHERE " . |
|
"di.destroy_date IS NOT NULL AND di.destroy_date >= ? " . |
|
"$prodcond $extracond" . |
|
"GROUP BY di.destroy_date ORDER BY di.destroy_date", |
|
array_merge(array($begdate), $prodbind, $extrabind) |
|
); |
|
while ($row = sqlFetchArray($res)) { |
|
$thisdate = substr($row['destroy_date'], 0, 10); |
|
if (!isset($qtys[$thisdate])) { |
|
$qtys[$thisdate] = 0; |
|
} |
|
$qtys[$thisdate] += $row['on_hand']; |
|
} |
|
|
|
// Add sums of other transactions for each date. |
|
// Note sales are positive and purchases are negative. |
|
$res = sqlStatement( |
|
"SELECT ds.sale_date, SUM(ds.quantity) AS quantity " . |
|
"FROM drug_sales AS ds, drug_inventory AS di " . |
|
"LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " . |
|
"lo.option_id = di.warehouse_id AND lo.activity = 1 " . |
|
"WHERE " . |
|
"ds.sale_date >= ? AND " . |
|
"di.inventory_id = ds.inventory_id " . |
|
"$prodcond $extracond" . |
|
"GROUP BY ds.sale_date ORDER BY ds.sale_date", |
|
array_merge(array($begdate), $prodbind, $extrabind) |
|
); |
|
while ($row = sqlFetchArray($res)) { |
|
$thisdate = $row['sale_date']; |
|
if (!isset($qtys[$thisdate])) { |
|
$qtys[$thisdate] = 0; |
|
} |
|
$qtys[$thisdate] += $row['quantity']; |
|
} |
|
|
|
// Subtract sums of transfers out for each date. |
|
// Quantity for transfers, like purchases, is usually negative. |
|
$res = sqlStatement( |
|
"SELECT ds.sale_date, SUM(ds.quantity) AS quantity " . |
|
"FROM drug_sales AS ds, drug_inventory AS di " . |
|
"LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " . |
|
"lo.option_id = di.warehouse_id AND lo.activity = 1 " . |
|
"WHERE " . |
|
"ds.sale_date >= ? AND " . |
|
"di.inventory_id = ds.xfer_inventory_id " . |
|
"$prodcond $extracond" . |
|
"GROUP BY ds.sale_date ORDER BY ds.sale_date", |
|
array_merge(array($begdate), $prodbind, $extrabind) |
|
); |
|
while ($row = sqlFetchArray($res)) { |
|
$thisdate = $row['sale_date']; |
|
if (!isset($qtys[$thisdate])) { |
|
$qtys[$thisdate] = 0; |
|
} |
|
$qtys[$thisdate] -= $row['quantity']; |
|
} |
|
|
|
// Sort by reverse date. |
|
krsort($qtys); |
|
|
|
$lastdate = ''; |
|
$lastqty = $current_qoh; |
|
|
|
// This will be the count of days that have zero quantity at the start of the day. |
|
$zerodays = 0; |
|
|
|
// Now we traverse the array in descending date order, adding a date's quantity adjustment |
|
// to the running total to get the quantity at the beginning of that date. |
|
foreach ($qtys as $key => $val) { |
|
if ($lastdate && $lastqty < $min_sale) { |
|
// The span of days from $key to start of $lastdate has zero quantity. |
|
// Add that number of days to $zerodays. |
|
$diff = date_diff(date_create($key), date_create($lastdate)); |
|
$zerodays += $diff->days; |
|
} |
|
$lastdate = $key; |
|
$lastqty += $val; // giving qoh at the start of $lastdate |
|
} |
|
// The last array entry hasn't been accounted for yet, so do that. |
|
if ($lastqty < $min_sale) { |
|
++$zerodays; |
|
} |
|
return $zerodays; |
|
} |
|
|
|
function write_report_line(&$row) |
|
{ |
|
global $form_details, $wrl_last_drug_id, $warnings, $encount, $fwcond, $fwbind, $form_days; |
|
global $gbl_expired_lot_warning_days, $form_facility, $form_warehouse, $form_action; |
|
|
|
$emptyvalue = $form_action != 'export' ? ' ' : ''; |
|
$drug_id = (int) $row['drug_id']; |
|
$on_hand = (int) $row['on_hand']; |
|
$warehouse_id = isset($row['warehouse_id']) ? $row['warehouse_id'] : ''; |
|
$facility_id = empty($row['option_value']) ? '0' : $row['option_value']; |
|
$warnings = ''; |
|
|
|
// Get sales in the date range for this drug (and facility or warehouse if details). |
|
if ($form_details == 1) { // facility details |
|
$query = "SELECT " . |
|
"SUM(s.quantity) AS sale_quantity " . |
|
"FROM drug_sales AS s " . |
|
"LEFT JOIN drug_inventory AS di ON di.inventory_id = s.inventory_id " . |
|
"LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " . |
|
"lo.option_id = di.warehouse_id AND lo.activity = 1 " . |
|
"WHERE " . |
|
"s.drug_id = ? AND " . |
|
"lo.option_value IS NOT NULL AND lo.option_value = ? AND " . |
|
"s.sale_date > DATE_SUB(NOW(), INTERVAL " . escape_limit($form_days) . " DAY) " . |
|
"AND s.pid != 0 $fwcond"; |
|
$srow = sqlQuery($query, array_merge(array($drug_id, $facility_id), $fwbind)); |
|
} else if ($form_details == 2) { // warehouse details |
|
$query = "SELECT " . |
|
"SUM(s.quantity) AS sale_quantity " . |
|
"FROM drug_sales AS s " . |
|
"LEFT JOIN drug_inventory AS di ON di.inventory_id = s.inventory_id " . |
|
"LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " . |
|
"lo.option_id = di.warehouse_id AND lo.activity = 1 " . |
|
"WHERE " . |
|
"s.drug_id = ? AND " . |
|
"di.warehouse_id IS NOT NULL AND di.warehouse_id = ? AND " . |
|
"s.sale_date > DATE_SUB(NOW(), INTERVAL " . escape_limit($form_days) . " DAY) " . |
|
"AND s.pid != 0 $fwcond"; |
|
$srow = sqlQuery($query, array_merge(array($drug_id, $warehouse_id), $fwbind)); |
|
} else { |
|
$srow = sqlQuery( |
|
"SELECT " . |
|
"SUM(s.quantity) AS sale_quantity " . |
|
"FROM drug_sales AS s " . |
|
"LEFT JOIN drug_inventory AS di ON di.inventory_id = s.inventory_id " . |
|
"LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " . |
|
"lo.option_id = di.warehouse_id AND lo.activity = 1 " . |
|
"WHERE " . |
|
"s.drug_id = ? AND " . |
|
"s.sale_date > DATE_SUB(NOW(), INTERVAL " . escape_limit($form_days) . " DAY) " . |
|
"AND s.pid != 0 $fwcond", |
|
array_merge(array($drug_id), $fwbind) |
|
); |
|
} |
|
$sale_quantity = $srow['sale_quantity']; |
|
|
|
// Compute the smallest quantity that might be taken from ANY lot for this product |
|
// (and facility or warehouse if details) based on the past $form_days days of sales. |
|
// If lot combining is allowed this is always 1. |
|
$extracond = $fwcond; |
|
$extrabind = $fwbind; |
|
if ($form_details == 1) { |
|
$extracond = "AND lo.option_value IS NOT NULL AND lo.option_value = ?"; |
|
$extrabind = array($facility_id); |
|
} |
|
if ($form_details == 2) { |
|
$extracond = "AND di.warehouse_id = ?"; |
|
$extrabind = array($warehouse_id); |
|
} |
|
$min_sale = 1; |
|
if (!$row['allow_combining']) { |
|
$sminrow = sqlQuery( |
|
"SELECT " . |
|
"MIN(s.quantity) AS min_sale " . |
|
"FROM drug_sales AS s " . |
|
"LEFT JOIN drug_inventory AS di ON di.drug_id = s.drug_id " . |
|
"LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " . |
|
"lo.option_id = di.warehouse_id AND lo.activity = 1 " . |
|
"WHERE " . |
|
"s.drug_id = ? AND " . |
|
"s.sale_date > DATE_SUB(NOW(), INTERVAL " . escape_limit($form_days) . " DAY) " . |
|
"AND s.pid != 0 " . |
|
"AND s.quantity > 0 $extracond", |
|
array_merge(array($drug_id), $extrabind) |
|
); |
|
$min_sale = 0 + $sminrow['min_sale']; |
|
} |
|
if (!$min_sale) { |
|
$min_sale = 1; |
|
} |
|
|
|
// Get number of days with no stock. |
|
$today = date('Y-m-d'); |
|
$tmp_days = max($form_days - 1, 0); |
|
$begdate = date('Y-m-d', strtotime("$today - $tmp_days days")); |
|
$zerodays = zeroDays($drug_id, $begdate, $extracond, $extrabind, $min_sale); |
|
|
|
$months = $form_days / 30.41; |
|
|
|
$monthly = ($months && $sale_quantity && $form_days > $zerodays) ? |
|
sprintf('%0.1f', $sale_quantity / $months * $form_days / ($form_days - $zerodays)) |
|
: 0; |
|
|
|
if ($monthly == 0.0 && $on_hand == 0) { |
|
// The row has no QOH and no recent sales, so is deemed uninteresting. |
|
// See CV email 2014-06-25. |
|
return; |
|
} |
|
|
|
if ($drug_id != $wrl_last_drug_id) { |
|
++$encount; |
|
} |
|
$bgcolor = "#" . (($encount & 1) ? "ddddff" : "ffdddd"); |
|
|
|
$stock_months = 0; |
|
if ($monthly != 0) { |
|
$stock_months = sprintf('%0.1f', $on_hand / $monthly); |
|
if ($stock_months < 1.0) { |
|
addWarning(xl('QOH is less than monthly usage')); |
|
} |
|
} |
|
|
|
// Check for reorder point reached, once per product. |
|
if ($drug_id != $wrl_last_drug_id) { |
|
if (checkReorder($drug_id, $row['reorder_point'])) { |
|
addWarning(xl('Product-level reorder point has been reached')); |
|
} |
|
} |
|
// For warehouse details mode we want the message on the line for this warehouse. |
|
// If the warehouse is not shown because it has no QOH and no recent |
|
// activity, then this message doesn't matter any more either. |
|
if ($form_details == 2) { |
|
if (checkReorder($drug_id, $row['pw_min_level'], $warehouse_id)) { |
|
addWarning(xl("Reorder point has been reached for warehouse") . |
|
" '" . $row['title'] . "'"); |
|
} |
|
} |
|
|
|
// Get all lots that we want to issue warnings about. These are lots |
|
// expired, soon to expire, or with insufficient quantity for selling. |
|
$gbl_expired_lot_warning_days = empty($gbl_expired_lot_warning_days) ? 0 : intval($gbl_expired_lot_warning_days); |
|
if ($gbl_expired_lot_warning_days <= 0) { |
|
$gbl_expired_lot_warning_days = 30; |
|
} |
|
$ires = sqlStatement( |
|
"SELECT di.* " . |
|
"FROM drug_inventory AS di " . |
|
"LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " . |
|
"lo.option_id = di.warehouse_id AND lo.activity = 1 " . |
|
"WHERE " . |
|
"di.drug_id = ? AND " . |
|
"di.on_hand > 0 AND " . |
|
"di.destroy_date IS NULL AND ( " . |
|
"di.on_hand < ? OR " . |
|
"di.expiration IS NOT NULL AND di.expiration < DATE_ADD(NOW(), INTERVAL " . escape_limit($gbl_expired_lot_warning_days) . " DAY) " . |
|
") $extracond ORDER BY di.lot_number", |
|
array_merge(array($drug_id, $min_sale), $extrabind) |
|
); |
|
// Generate warnings associated with individual lots. |
|
while ($irow = sqlFetchArray($ires)) { |
|
$lotno = $irow['lot_number']; |
|
if ($irow['on_hand'] < $min_sale) { |
|
addWarning(xl('Lot') . " '$lotno' " . xl('quantity seems unusable')); |
|
} |
|
if (!empty($irow['expiration'])) { |
|
$expdays = (int) ((strtotime($irow['expiration']) - time()) / (60 * 60 * 24)); |
|
if ($expdays <= 0) { |
|
addWarning(xl('Lot') . " '$lotno' " . xl('has expired')); |
|
} else if ($expdays <= $gbl_expired_lot_warning_days) { |
|
addWarning(xl('Lot') . " '$lotno' " . xl('expires in') . " $expdays " . xl('days')); |
|
} |
|
} |
|
} |
|
|
|
// Per CV 2014-06-20: |
|
// Reorder Quantity should be calculated only if Stock Months is less than Months Min. |
|
// If Stock Months is [not] less than Months Min, Reorder Quantity should be zero. |
|
// The calculation should be: (Min Months minus Stock Months) times Avg Monthly. |
|
// Reorder Quantity should be rounded up to a whole number. |
|
$reorder_qty = 0; |
|
if ($monthly > 0.00) { |
|
// Note if facility details, this the sum of min levels for the facility's warehouses. |
|
$min_months = 0 + ($form_details ? $row['pw_min_level'] : $row['reorder_point']); |
|
// If min is not specified as months then compute it that way. |
|
if (empty($GLOBALS['gbl_min_max_months'])) { |
|
$min_months /= $monthly; |
|
} |
|
if ($stock_months < $min_months) { |
|
$reorder_qty = ceil(($min_months - $stock_months) * $monthly); |
|
} |
|
} |
|
|
|
if (empty($monthly)) { |
|
$monthly = $emptyvalue; |
|
} |
|
if (empty($stock_months)) { |
|
$stock_months = $emptyvalue; |
|
} |
|
|
|
$relcodes = ''; |
|
$tmp = explode(';', $row['related_code']); |
|
foreach ($tmp as $codestring) { |
|
if ($codestring === '') { |
|
continue; |
|
} |
|
list($codetype, $code) = explode(':', $codestring); |
|
// For IPPF just the IPPFCM codes are wanted. |
|
if ($GLOBALS['ippf_specific'] && $codetype !== 'IPPFCM') { |
|
continue; |
|
} |
|
if ($relcodes) { |
|
$relcodes .= ';'; |
|
} |
|
$relcodes .= $codestring; |
|
} |
|
|
|
$drug_form = empty($row['form']) ? '' : generate_display_field( |
|
array( |
|
'data_type' => '1', |
|
'list_id' => 'drug_form' |
|
), |
|
$row['form'] |
|
); |
|
|
|
if ($form_action == 'export') { |
|
echo csvEscape($row['name']) . ','; |
|
echo csvEscape($relcodes) . ','; |
|
echo csvEscape($row['ndc_number']) . ','; |
|
echo csvEscape($row['active'] ? xl('Yes') : xl('No')) . ','; |
|
echo csvEscape($row['consumable'] ? xl('Yes') : xl('No')) . ','; |
|
echo csvEscape($drug_form) . ','; |
|
if ($form_details) { |
|
echo csvEscape($row['facname']) . ','; |
|
if ($form_details == 2) { // warehouse details { |
|
echo csvEscape($row['title']) . ','; |
|
} |
|
echo csvEscape($row['pw_min_level']) . ','; |
|
echo csvEscape($row['pw_max_level']) . ','; |
|
} else { |
|
echo csvEscape($row['reorder_point']) . ','; |
|
echo csvEscape($row['max_level']) . ','; |
|
} |
|
echo csvEscape($row['on_hand']) . ','; |
|
echo csvEscape($zerodays) . ','; |
|
echo csvEscape($monthly) . ','; |
|
echo csvEscape($stock_months) . ','; |
|
echo csvEscape($reorder_qty) . ','; |
|
echo csvEscape($warnings) . ''; |
|
echo "\n"; |
|
} else { // end exporting |
|
echo " <tr class='detail' bgcolor='$bgcolor'>\n"; |
|
if ($drug_id == $wrl_last_drug_id) { |
|
echo " <td colspan='6'> </td>\n"; |
|
} else { |
|
echo " <td>" . text($row['name']) . "</td>\n"; |
|
echo " <td>" . text($relcodes) . "</td>\n"; |
|
echo " <td>" . text($row['ndc_number']) . "</td>\n"; |
|
echo " <td>" . ($row['active'] ? xlt('Yes') : xlt('No')) . "</td>\n"; |
|
echo " <td>" . ($row['consumable'] ? xlt('Yes') : xlt('No')) . "</td>\n"; |
|
echo " <td>" . text($drug_form) . "</td>\n"; |
|
} |
|
if ($form_details) { |
|
echo " <td>" . text($row['facname']) . "</td>\n"; |
|
if ($form_details == 2) { // warehouse details { |
|
echo " <td>" . text($row['title']) . "</td>\n"; |
|
} |
|
echo " <td align='right'>" . text($row['pw_min_level']) . "</td>\n"; |
|
echo " <td align='right'>" . text($row['pw_max_level']) . "</td>\n"; |
|
} else { |
|
echo " <td align='right'>" . text($row['reorder_point']) . "</td>\n"; |
|
echo " <td align='right'>" . text($row['max_level']) . "</td>\n"; |
|
} |
|
echo " <td align='right'>" . text($row['on_hand']) . "</td>\n"; |
|
echo " <td align='right'>" . text($zerodays) . "</td>\n"; |
|
// Do not text() these next 2 lines. |
|
echo " <td align='right'>" . $monthly . "</td>\n"; |
|
echo " <td align='right'>" . $stock_months . "</td>\n"; |
|
echo " <td align='right'>" . text($reorder_qty) . "</td>\n"; |
|
// Do not text() the following line. |
|
echo " <td style='color:red'>" . $warnings . "</td>\n"; |
|
echo " </tr>\n"; |
|
} // end not exporting |
|
|
|
$wrl_last_drug_id = $drug_id; |
|
} |
|
|
|
if (!empty($_POST['form_days'])) { |
|
$form_days = $_POST['form_days'] + 0; |
|
} else { |
|
$form_days = sprintf('%d', (strtotime(date('Y-m-d')) - strtotime(date('Y-01-01'))) / (60 * 60 * 24) + 1); |
|
} |
|
|
|
// this is "" or "submit". |
|
$form_action = $_POST['form_action'] ?? ''; |
|
|
|
if (!empty($_POST['form_days'])) { |
|
$form_days = $_POST['form_days'] + 0; |
|
} else { |
|
$form_days = sprintf('%d', (strtotime(date('Y-m-d')) - strtotime(date('Y-01-01'))) / (60 * 60 * 24) + 1); |
|
} |
|
|
|
// this is "" or "submit" or "export". |
|
$form_action = empty($_POST['form_action']) ? '' : $_POST['form_action']; |
|
|
|
$form_inactive = empty($_REQUEST['form_inactive']) ? 0 : 1; |
|
$form_details = empty($_REQUEST['form_details']) ? 0 : intval($_REQUEST['form_details']); |
|
$form_facility = 0 + empty($_REQUEST['form_facility']) ? 0 : $_REQUEST['form_facility']; |
|
$form_consumable = isset($_REQUEST['form_consumable']) ? intval($_REQUEST['form_consumable']) : 0; |
|
$form_orderby = $_REQUEST['form_orderby'] ?? 'name'; |
|
$orderby = $ORDERHASH[$form_orderby]; |
|
|
|
// Incoming form_warehouse, if not empty is in the form "warehouse/facility". |
|
// The facility part is an attribute used by JavaScript logic. |
|
$form_warehouse = $_REQUEST['form_warehouse'] ?? ''; |
|
$tmp = explode('/', $form_warehouse); |
|
$form_warehouse = $tmp[0]; |
|
|
|
$mmtype = empty($GLOBALS['gbl_min_max_months']) ? xl('Units') : xl('Months'); |
|
|
|
// Compute WHERE condition for filtering on facility/warehouse. |
|
$fwcond = ''; |
|
$fwbind = array(); |
|
if ($form_facility) { |
|
$fwcond .= " AND lo.option_value IS NOT NULL AND lo.option_value = ?"; |
|
$fwbind[] = $form_facility; |
|
} |
|
if ($form_warehouse) { |
|
$fwcond .= " AND di.warehouse_id IS NOT NULL AND di.warehouse_id = ?"; |
|
$fwbind[] = $form_warehouse; |
|
} |
|
if ($is_user_restricted) { |
|
$fwcond .= "AND di.warehouse_id IS NOT NULL AND di.warehouse_id IN (" . genUserWarehouses() . ")"; |
|
} |
|
|
|
// Compute WHERE condition for filtering on activity and consumability (drugs table). |
|
$actcond = ''; |
|
if (!$form_inactive) { |
|
$actcond .= " AND d.active = 1"; |
|
} |
|
if ($form_consumable) { |
|
if ($form_consumable == 1) { |
|
$actcond .= " AND d.consumable = '1'"; |
|
} else { |
|
$actcond .= " AND d.consumable != '1'"; |
|
} |
|
} |
|
|
|
if ($form_details == 1) { |
|
// Query for the main loop if facility details are wanted. |
|
$query = "SELECT d.*, SUM(di.on_hand) AS on_hand, lo.option_value, fac.name AS facname, " . |
|
"SUM(pw.pw_min_level) AS pw_min_level, SUM(pw.pw_max_level) AS pw_max_level " . |
|
"FROM drugs AS d " . |
|
"LEFT JOIN drug_inventory AS di ON di.drug_id = d.drug_id " . |
|
"AND di.on_hand != 0 AND di.destroy_date IS NULL " . |
|
"LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " . |
|
"lo.option_id = di.warehouse_id AND lo.activity = 1 " . |
|
"LEFT JOIN facility AS fac ON fac.id = lo.option_value " . |
|
"LEFT JOIN product_warehouse AS pw ON pw.pw_drug_id = d.drug_id AND " . |
|
"pw.pw_warehouse = di.warehouse_id " . |
|
"WHERE 1 = 1 $fwcond $actcond " . |
|
"GROUP BY d.name, d.drug_id, lo.option_value ORDER BY $orderby, lo.option_value"; |
|
} else if ($form_details == 2) { |
|
// Query for the main loop if warehouse/lot details are wanted. |
|
$query = "SELECT d.*, di.on_hand, di.inventory_id, di.lot_number, " . |
|
"di.expiration, di.warehouse_id, lo.title, fac.name AS facname, " . |
|
"pw.pw_min_level, pw.pw_max_level " . |
|
"FROM drugs AS d " . |
|
"LEFT JOIN drug_inventory AS di ON di.drug_id = d.drug_id " . |
|
"AND di.on_hand != 0 AND di.destroy_date IS NULL " . |
|
"LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " . |
|
"lo.option_id = di.warehouse_id AND lo.activity = 1 " . |
|
"LEFT JOIN facility AS fac ON fac.id = lo.option_value " . |
|
"LEFT JOIN product_warehouse AS pw ON pw.pw_drug_id = d.drug_id AND " . |
|
"pw.pw_warehouse = di.warehouse_id " . |
|
"WHERE 1 = 1 $fwcond $actcond " . |
|
"ORDER BY $orderby, lo.title, di.warehouse_id, di.lot_number, di.inventory_id"; |
|
} else { |
|
// Query for the main loop if summary report. |
|
$query = "SELECT d.*, SUM(di.on_hand) AS on_hand " . |
|
"FROM drugs AS d " . |
|
"LEFT JOIN drug_inventory AS di ON di.drug_id = d.drug_id " . |
|
"AND di.on_hand != 0 AND di.destroy_date IS NULL " . |
|
"LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " . |
|
"lo.option_id = di.warehouse_id AND lo.activity = 1 " . |
|
"WHERE 1 = 1 $fwcond $actcond " . |
|
"GROUP BY $orderby ORDER BY $orderby"; |
|
} |
|
|
|
// $res = sqlStatement($query); |
|
$res = sqlStatement($query, $fwbind); |
|
|
|
if ($form_action == 'export') { |
|
header("Pragma: public"); |
|
header("Expires: 0"); |
|
header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); |
|
header("Content-Type: application/force-download; charset=utf-8"); |
|
header("Content-Disposition: attachment; filename=inventory_list.csv"); |
|
header("Content-Description: File Transfer"); |
|
// Prepend a BOM (Byte Order Mark) header to mark the data as UTF-8. This is |
|
// said to work for Excel 2007 pl3 and up and perhaps also Excel 2003 pl3. See: |
|
// http://stackoverflow.com/questions/155097/microsoft-excel-mangles-diacritics-in-csv-files |
|
// http://crashcoursing.blogspot.com/2011/05/exporting-csv-with-special-characters.html |
|
echo "\xEF\xBB\xBF"; |
|
|
|
// CSV headers: |
|
echo csvEscape(xl('Name')) . ','; |
|
echo csvEscape(xl('Relates To')) . ','; |
|
echo csvEscape(xl('NDC')) . ','; |
|
echo csvEscape(xl('Active')) . ','; |
|
echo csvEscape(xl('Consumable')) . ','; |
|
echo csvEscape(xl('Form')) . ','; |
|
if ($form_details) { |
|
echo csvEscape(xl('Facility')) . ','; |
|
if ($form_details == 2) { |
|
echo csvEscape(xl('Warehouse')) . ','; |
|
} |
|
echo csvEscape($mmtype . xl('Min')) . ','; |
|
echo csvEscape($mmtype . xl('Max')) . ','; |
|
} |
|
echo csvEscape(xl('QOH')) . ','; |
|
echo csvEscape(xl('Zero Stock Days')) . ','; |
|
echo csvEscape(xl('Avg Monthly')) . ','; |
|
echo csvEscape(xl('Stock Months')) . ','; |
|
echo csvEscape(xl('Reorder Qty')) . ','; |
|
echo csvEscape(xl('Warnings')) . ''; |
|
echo "\n"; |
|
} else { // not exporting |
|
?> |
|
<html> |
|
|
|
<head> |
|
|
|
<title><?php echo xlt('Inventory List'); ?></title> |
|
|
|
<?php Header::setupHeader(['report-helper']); ?> |
|
|
|
<style> |
|
/* specifically include & exclude from printing */ |
|
@media print { |
|
#report_parameters {visibility: hidden; display: none;} |
|
#report_parameters_daterange {visibility: visible; display: inline;} |
|
#report_results {margin-top: 30px;} |
|
} |
|
/* specifically exclude some from the screen */ |
|
@media screen { |
|
#report_parameters_daterange {visibility: hidden; display: none;} |
|
} |
|
|
|
body { font-family:sans-serif; font-size:10pt; font-weight:normal } |
|
|
|
tr.head { font-size:10pt; background-color:#cccccc; text-align:center; } |
|
tr.detail { font-size:10pt; } |
|
a, a:visited, a:hover { color:#0000cc; } |
|
|
|
table.mymaintable, table.mymaintable td, table.mymaintable th { |
|
border: 1px solid #aaaaaa; |
|
border-collapse: collapse; |
|
} |
|
table.mymaintable td, table.mymaintable th { |
|
padding: 1pt 4pt 1pt 4pt; |
|
} |
|
</style> |
|
|
|
<script> |
|
|
|
$(function () { |
|
oeFixedHeaderSetup(document.getElementById('mymaintable')); |
|
var win = top.printLogSetup ? top : opener.top; |
|
win.printLogSetup(document.getElementById('printbutton')); |
|
}); |
|
|
|
function mysubmit(action) { |
|
var f = document.forms[0]; |
|
f.form_action.value = action; |
|
top.restoreSession(); |
|
f.submit(); |
|
} |
|
|
|
function dosort(orderby) { |
|
var f = document.forms[0]; |
|
f.form_orderby.value = orderby; |
|
f.form_action.value = 'submit'; |
|
top.restoreSession(); |
|
f.submit(); |
|
return false; |
|
} |
|
|
|
// Enable/disable warehouse options depending on current facility. |
|
function facchanged() { |
|
var f = document.forms[0]; |
|
var facid = f.form_facility.value; |
|
var theopts = f.form_warehouse.options; |
|
for (var i = 1; i < theopts.length; ++i) { |
|
var tmp = theopts[i].value.split('/'); |
|
var dis = facid && (tmp.length < 2 || tmp[1] != facid); |
|
theopts[i].disabled = dis; |
|
if (dis) theopts[i].selected = false; |
|
} |
|
} |
|
|
|
</script> |
|
|
|
</head> |
|
|
|
<body leftmargin='0' topmargin='0' marginwidth='0' marginheight='0' class='body_top'> |
|
|
|
<center> |
|
|
|
<form method='post' action='inventory_list.php' name='theform' onsubmit='return top.restoreSession()'> |
|
<input type="hidden" name="csrf_token_form" value="<?php echo attr(CsrfUtils::collectCsrfToken()); ?>" /> |
|
|
|
<!-- form_action is set to "submit" or "export" at form submit time --> |
|
<input type='hidden' name='form_action' value='' /> |
|
|
|
<!-- Sorting key --> |
|
<input type="hidden" name="form_orderby" value="<?php echo attr($form_orderby) ?>" /> |
|
|
|
<div id="report_parameters"> |
|
<table> |
|
<tr> |
|
<td class='title'> |
|
<?php echo xlt('Inventory List'); ?> |
|
</td> |
|
<td class='text' align='right'> |
|
<?php |
|
// Build a drop-down list of facilities. |
|
// |
|
$query = "SELECT id, name FROM facility ORDER BY name"; |
|
$fres = sqlStatement($query); |
|
echo " <select name='form_facility' onchange='facchanged()'>\n"; |
|
echo " <option value=''>-- " . xlt('All Facilities') . " --\n"; |
|
while ($frow = sqlFetchArray($fres)) { |
|
$facid = $frow['id']; |
|
if ($is_user_restricted && !isFacilityAllowed($facid)) { |
|
continue; |
|
} |
|
echo " <option value='" . attr($facid) . "'"; |
|
if ($facid == $form_facility) { |
|
echo " selected"; |
|
} |
|
echo ">" . text($frow['name']) . "\n"; |
|
} |
|
echo " </select> \n"; |
|
echo " <select name='form_warehouse'>\n"; |
|
echo " <option value=''>" . xlt('All Warehouses') . "</option>\n"; |
|
$lres = sqlStatement( |
|
"SELECT * FROM list_options " . |
|
"WHERE list_id = 'warehouse' AND activity = 1 ORDER BY seq, title" |
|
); |
|
while ($lrow = sqlFetchArray($lres)) { |
|
$whid = $lrow['option_id']; |
|
$facid = $lrow['option_value']; |
|
if ($is_user_restricted && !isWarehouseAllowed($facid, $whid)) { |
|
continue; |
|
} |
|
echo " <option value='" . attr($whid . "/" . $facid) . "'"; |
|
echo " id='fac" . attr($facid) . "'"; |
|
if (strlen($form_warehouse) > 0 && $whid == $form_warehouse) { |
|
echo " selected"; |
|
} |
|
echo ">" . text(xl_list_label($lrow['title'])) . "</option>\n"; |
|
} |
|
echo " </select> \n"; |
|
?> |
|
|
|
<?php echo xlt('For the past'); ?> |
|
<input type="input" name="form_days" size='3' value="<?php echo attr($form_days); ?>" /> |
|
<?php echo xlt('days'); ?> |
|
|
|
<select name='form_consumable'><?php |
|
foreach ( |
|
array( |
|
'0' => xl('All Product Types'), |
|
'1' => xl('Consumable Only'), |
|
'2' => xl('Non-Consumable Only'), |
|
) as $key => $value |
|
) { |
|
echo "<option value='" . attr($key) . "'"; |
|
if ($key == $form_consumable) { |
|
echo " selected"; |
|
} |
|
echo ">" . text($value) . "</option>"; |
|
} |
|
?></select> |
|
|
|
<input type='checkbox' name='form_inactive' value='1'<?php if ($form_inactive) { |
|
echo " checked";} ?> |
|
/><?php echo xlt('Include Inactive'); ?> |
|
|
|
<?php |
|
echo " <select name='form_details'>\n"; |
|
$tmparr = array(0 => xl('Summary'), 1 => xl('Facility Details'), 2 => xl('Warehouse Details')); |
|
foreach ($tmparr as $key => $value) { |
|
echo " <option value='" . attr($key) . "'"; |
|
if ($key == $form_details) { |
|
echo " selected"; |
|
} |
|
echo ">" . text($value) . "\n"; |
|
} |
|
echo " </select> \n"; |
|
?> |
|
<a href='#' class='btn btn-primary' onclick='mysubmit("submit")' style='margin-left:1em'> |
|
<span><?php echo xlt('Refresh'); ?></span> |
|
</a> |
|
<a href='#' class='btn btn-primary' onclick='mysubmit("export")' style='margin-left:1em'> |
|
<span><?php echo xlt('Export to CSV'); ?></span> |
|
</a> |
|
<a href='#' class='btn btn-primary' onclick='window.print()' style='margin-left:1em' id='printbutton'> |
|
<span><?php echo xlt('Print'); ?></span> |
|
</a> |
|
</td> |
|
</tr> |
|
</table> |
|
</div> |
|
|
|
<?php |
|
if ($form_action) { |
|
// We are not exporting and have submitted the form. |
|
?> |
|
<div id="report_results"> |
|
<table width='98%' id='mymaintable' class='mymaintable'> |
|
<thead style='display:table-header-group'> |
|
<tr class='head'> |
|
<th> |
|
<a href="#" onclick="return dosort('name')" |
|
<?php if ($form_orderby == "name") { |
|
echo " style=\"color:#00cc00\"";} ?>> |
|
<?php echo xlt('Name'); ?> </a> |
|
</th> |
|
<th><?php echo xlt('Relates To'); ?></th> |
|
<th><?php echo xlt('NDC'); ?></th> |
|
<th> |
|
<a href="#" onclick="return dosort('act')" |
|
<?php echo $form_orderby == "act" ? " style=\"color:#00cc00\"" : ""; ?>> |
|
<?php echo xlt('Active'); ?> </a> |
|
</th> |
|
<th> |
|
<a href="#" onclick="return dosort('con')" |
|
<?php echo $form_orderby == "con" ? " style=\"color:#00cc00\"" : ""; ?>> |
|
<?php echo xlt('Consumable'); ?> </a> |
|
</th> |
|
<th><?php echo xlt('Form'); ?></th> |
|
<?php if ($form_details) { ?> |
|
<th><?php echo xlt('Facility'); ?></th> |
|
<?php if ($form_details == 2) { ?> |
|
<th><?php echo xlt('Warehouse'); ?></th> |
|
<?php } ?> |
|
<?php } ?> |
|
<th align='right'><?php echo text("$mmtype " . xl('Min')); ?></th> |
|
<th align='right'><?php echo text("$mmtype " . xl('Max')); ?></th> |
|
<th align='right'><?php echo xlt('QOH'); ?></th> |
|
<th align='right'><?php echo xlt('Zero Stock Days'); ?></th> |
|
<th align='right'><?php echo xlt('Avg Monthly'); ?></th> |
|
<th align='right'><?php echo xlt('Stock Months'); ?></th> |
|
<th align='right'><?php echo xlt('Reorder Qty'); ?></th> |
|
<th><?php echo xlt('Warnings'); ?></th> |
|
</tr> |
|
</thead> |
|
<tbody> |
|
<?php |
|
} // end if submit |
|
} // end not exporting |
|
|
|
if ($form_action) { // if submit |
|
$encount = 0; |
|
$last_drug_id = ''; |
|
$wrl_last_drug_id = ''; |
|
$warehouse_row = array('drug_id' => 0, 'warehouse_id' => ''); |
|
|
|
while ($row = sqlFetchArray($res)) { |
|
$drug_id = (int) $row['drug_id']; |
|
if ($form_details == 2) { |
|
if ($drug_id != $last_drug_id || $row['warehouse_id'] != $warehouse_row['warehouse_id']) { |
|
if (!empty($warehouse_row['drug_id'])) { |
|
write_report_line($warehouse_row); |
|
} |
|
$warehouse_row = $row; |
|
$warehouse_row['on_hand'] = 0; |
|
} |
|
$warehouse_row['on_hand'] += $row['on_hand']; |
|
} else { |
|
write_report_line($row); |
|
} |
|
$last_drug_id = $drug_id; |
|
} |
|
|
|
if ($form_details == 2) { |
|
if (!empty($warehouse_row['drug_id'])) { |
|
write_report_line($warehouse_row); |
|
} |
|
} |
|
|
|
if ($form_action != 'export') { |
|
echo " </tbody>\n"; |
|
echo "</table>\n"; |
|
echo "</div>\n"; |
|
} |
|
} // end if submit |
|
|
|
if ($form_action != 'export') { |
|
?> |
|
</form> |
|
</center> |
|
</body> |
|
</html> |
|
<?php |
|
} // end not export
|
|
|