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.
 
 
 
 
 
 

391 lines
13 KiB

<?php
// Copyright (C) 2006-2016 Rod Roark <rod@sunsetsystems.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// Modified 7-2009 by BM in order to migrate using the form,
// unit, route, and interval lists with the
// functions in openemr/library/options.inc.php .
// These lists are based on the constants found in the
// openemr/library/classes/Prescription.class.php file.
use PHPMailer\PHPMailer\PHPMailer;
// Decision was made in June 2013 that a sale line item in the Fee Sheet may
// come only from the specified warehouse. Set this to false if the decision
// is reversed.
$GLOBALS['SELL_FROM_ONE_WAREHOUSE'] = true;
$substitute_array = array('', xl('Allowed'), xl('Not Allowed'));
function send_drug_email($subject, $body)
{
$recipient = $GLOBALS['practice_return_email_path'];
if (empty($recipient)) {
return;
}
$mail = new PHPMailer();
$mail->From = $recipient;
$mail->FromName = 'In-House Pharmacy';
$mail->isMail();
$mail->Host = "localhost";
$mail->Mailer = "mail";
$mail->Body = $body;
$mail->Subject = $subject;
$mail->AddAddress($recipient);
if (!$mail->Send()) {
error_log("There has been a mail error sending to " . errorLogEscape($recipient .
" " . $mail->ErrorInfo));
}
}
function sellDrug(
$drug_id,
$quantity,
$fee,
$patient_id = 0,
$encounter_id = 0,
$prescription_id = 0,
$sale_date = '',
$user = '',
$default_warehouse = '',
$testonly = false,
&$expiredlots = null,
$pricelevel = '',
$selector = ''
) {
if (empty($patient_id)) {
$patient_id = $GLOBALS['pid'];
}
if (empty($sale_date)) {
$sale_date = date('Y-m-d');
}
if (empty($user)) {
$user = $_SESSION['authUser'];
}
// error_log("quantity = '$quantity'"); // debugging
// Sanity check.
if (!$testonly) {
$tmp = sqlQuery(
"SELECT count(*) AS count from form_encounter WHERE pid = ? AND encounter = ?",
array($patient_id, $encounter_id)
);
if (empty($tmp['count'])) {
die(xlt('Internal error: the referenced encounter no longer exists.') . text(" $patient_id $encounter_id"));
}
}
if (empty($default_warehouse)) {
// Get the default warehouse, if any, for the user.
$rowuser = sqlQuery("SELECT default_warehouse FROM users WHERE username = ?", array($user));
$default_warehouse = $rowuser['default_warehouse'];
}
// Get relevant options for this product.
$rowdrug = sqlQuery("SELECT allow_combining, reorder_point, name, dispensable " .
"FROM drugs WHERE drug_id = ?", array($drug_id));
$allow_combining = $rowdrug['allow_combining'];
$dispensable = $rowdrug['dispensable'];
if (!$dispensable) {
// Non-dispensable is a much simpler case and does not touch inventory.
if ($testonly) {
return true;
}
$sale_id = sqlInsert(
"INSERT INTO drug_sales ( " .
"drug_id, inventory_id, prescription_id, pid, encounter, user, " .
"sale_date, quantity, fee ) VALUES ( " .
"?, 0, ?, ?, ?, ?, ?, ?, ?)",
array($drug_id, $prescription_id, $patient_id, $encounter_id, $user, $sale_date, $quantity, $fee)
);
return $sale_id;
}
// Combining is never allowed for prescriptions and will not work with
// dispense_drug.php.
if ($prescription_id) {
$allow_combining = 0;
}
$rows = array();
// $firstrow = false;
$qty_left = $quantity;
$bad_lot_list = '';
$total_on_hand = 0;
$gotexpired = false;
// If the user has a default warehouse, sort those lots first.
$orderby = ($default_warehouse === '') ?
"" : "di.warehouse_id != '$default_warehouse', ";
$orderby .= "lo.seq, di.expiration, di.lot_number, di.inventory_id";
// Retrieve lots in order of expiration date within warehouse preference.
$query = "SELECT di.*, lo.option_id, lo.seq " .
"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.destroy_date IS NULL AND di.on_hand != 0 ";
$sqlarr = array($drug_id);
if ($GLOBALS['SELL_FROM_ONE_WAREHOUSE'] && $default_warehouse) {
$query .= "AND di.warehouse_id = ? ";
$sqlarr[] = $default_warehouse;
}
$query .= "ORDER BY $orderby";
$res = sqlStatement($query, $sqlarr);
// First pass. Pick out lots to be used in filling this order, figure out
// if there is enough quantity on hand and check for lots to be destroyed.
while ($row = sqlFetchArray($res)) {
if ($row['warehouse_id'] != $default_warehouse) {
// Warehouses with seq > 99 are not available.
$seq = empty($row['seq']) ? 0 : $row['seq'] + 0;
if ($seq > 99) {
continue;
}
}
$on_hand = $row['on_hand'];
$expired = (!empty($row['expiration']) && $row['expiration'] <= $sale_date);
if ($expired || $on_hand < $quantity) {
$tmp = $row['lot_number'];
if (! $tmp) {
$tmp = '[missing lot number]';
}
if ($bad_lot_list) {
$bad_lot_list .= ', ';
}
$bad_lot_list .= $tmp;
}
if ($expired) {
$gotexpired = true;
continue;
}
/*****************************************************************
// Note the first row in case total quantity is insufficient and we are
// allowed to go negative.
if (!$firstrow) $firstrow = $row;
*****************************************************************/
$total_on_hand += $on_hand;
if ($on_hand > 0 && $qty_left > 0 && ($allow_combining || $on_hand >= $qty_left)) {
$rows[] = $row;
$qty_left -= $on_hand;
}
}
if ($expiredlots !== null) {
$expiredlots = $gotexpired;
}
if ($testonly) {
// Just testing inventory, so return true if OK, false if insufficient.
// $qty_left, if positive, is the amount requested that could not be allocated.
return $qty_left <= 0;
}
if ($bad_lot_list) {
send_drug_email(
"Possible lot destruction needed",
"The following lot(s) are expired or were too small to fill the " .
"order for patient $patient_id: $bad_lot_list\n"
);
}
/*******************************************************************
if (empty($firstrow)) return 0; // no suitable lots exist
// This can happen when combining is not allowed. We will use the
// first row and take it negative.
if (empty($rows)) {
$rows[] = $firstrow;
$qty_left -= $firstrow['on_hand'];
}
*******************************************************************/
// The above was an experiment in permitting a negative lot quantity.
// We decided that was a bad idea, so now we just error out if there
// is not enough on hand.
if ($qty_left > 0) {
return 0;
}
$sale_id = 0;
$qty_final = $quantity; // remaining unallocated quantity
$fee_final = $fee; // remaining unallocated fee
// Second pass. Update the database.
foreach ($rows as $row) {
$inventory_id = $row['inventory_id'];
/*****************************************************************
$thisqty = $row['on_hand'];
if ($qty_left > 0) {
$thisqty += $qty_left;
$qty_left = 0;
}
else if ($thisqty > $qty_final) {
$thisqty = $qty_final;
}
*****************************************************************/
$thisqty = min($qty_final, $row['on_hand']);
$qty_final -= $thisqty;
// Compute the proportional fee for this line item. For the last line
// item take the remaining unallocated fee to avoid round-off error.
if ($qty_final) {
$thisfee = sprintf('%0.2f', $fee * $thisqty / $quantity);
} else {
$thisfee = sprintf('%0.2f', $fee_final);
}
$fee_final -= $thisfee;
// Update inventory and create the sale line item.
sqlStatement("UPDATE drug_inventory SET " .
"on_hand = on_hand - ? " .
"WHERE inventory_id = ?", array($thisqty,$inventory_id));
$sale_id = sqlInsert(
"INSERT INTO drug_sales ( " .
"drug_id, inventory_id, prescription_id, pid, encounter, user, sale_date, quantity, fee, pricelevel, selector ) " .
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
array($drug_id, $inventory_id, $prescription_id, $patient_id, $encounter_id, $user,
$sale_date,
$thisqty,
$thisfee,
$pricelevel,
$selector)
);
// If this sale exhausted the lot then auto-destroy it if that is wanted.
if ($row['on_hand'] == $thisqty && !empty($GLOBALS['gbl_auto_destroy_lots'])) {
sqlStatement(
"UPDATE drug_inventory SET " .
"destroy_date = ?, destroy_method = ?, destroy_witness = ?, destroy_notes = ? " .
"WHERE drug_id = ? AND inventory_id = ?",
array($sale_date, xl('Automatic from sale'), $user, "sale_id = $sale_id",
$drug_id,
$inventory_id)
);
}
}
/*******************************************************************
// If appropriate, generate email to notify that re-order is due.
if (($total_on_hand - $quantity) <= $rowdrug['reorder_point']) {
send_drug_email("Product re-order required",
"Product '" . $rowdrug['name'] . "' has reached its reorder point.\n");
}
// TBD: If the above is un-commented, fix it to handle the case of
// $GLOBALS['gbl_min_max_months'] being true.
*******************************************************************/
// If combining is allowed then $sale_id will be just the last inserted ID,
// and it serves only to indicate that everything worked. Otherwise there
// can be only one inserted row and this is its ID.
return $sale_id;
}
// Determine if facility and warehouse restrictions are applicable for this user.
function isUserRestricted($userid = 0)
{
if (!$userid) {
$userid = $_SESSION['authUserID'];
}
$countrow = sqlQuery("SELECT count(*) AS count FROM users_facility WHERE " .
"tablename = 'users' AND table_id = ?", array($userid));
return !empty($countrow['count']);
}
// Check if the user has access to the given facility.
// Do not call this if user is not restricted!
function isFacilityAllowed($facid, $userid = 0)
{
if (!$userid) {
$userid = $_SESSION['authUserID'];
}
$countrow = sqlQuery(
"SELECT count(*) AS count FROM users_facility WHERE " .
"tablename = 'users' AND table_id = ? AND facility_id = ?",
array($userid, $facid)
);
if (empty($countrow['count'])) {
$countrow = sqlQuery(
"SELECT count(*) AS count FROM users WHERE " .
"id = ? AND facility_id = ?",
array($userid, $facid)
);
return !empty($countrow['count']);
}
return true;
}
// Check if the user has access to the given warehouse within the given facility.
// Do not call this if user is not restricted!
function isWarehouseAllowed($facid, $whid, $userid = 0)
{
if (!$userid) {
$userid = $_SESSION['authUserID'];
}
$countrow = sqlQuery(
"SELECT count(*) AS count FROM users_facility WHERE " .
"tablename = 'users' AND table_id = ? AND facility_id = ? AND " .
"(warehouse_id = ? OR warehouse_id = '')",
array($userid, $facid, $whid)
);
if (empty($countrow['count'])) {
$countrow = sqlQuery(
"SELECT count(*) AS count FROM users WHERE " .
"id = ? AND default_warehouse = ?",
array($userid, $whid)
);
return !empty($countrow['count']);
}
return true;
}
// Determine if this product is one that we have on hand and that the user has permission for.
//
function isProductSelectable($drug_id)
{
$is_user_restricted = isUserRestricted();
$wfres = sqlStatement(
"SELECT di.warehouse_id, lo.option_value AS facid " .
"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.destroy_date IS NULL AND di.on_hand > 0 AND " .
"(di.expiration IS NULL OR di.expiration > NOW())",
array($drug_id)
);
while ($wfrow = sqlFetchArray($wfres)) {
if ($is_user_restricted && !isWarehouseAllowed($wfrow['facid'], $wfrow['warehouse_id'])) {
continue;
}
return true;
}
return false;
}