Highway to PSR2
[openemr.git] / interface / drugs / drugs.inc.php
blob27d088623d3576cdea39ad4eebc8f74890165afb
1 <?php
2 // Copyright (C) 2006-2016 Rod Roark <rod@sunsetsystems.com>
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
8 //
9 // Modified 7-2009 by BM in order to migrate using the form,
10 // unit, route, and interval lists with the
11 // functions in openemr/library/options.inc.php .
12 // These lists are based on the constants found in the
13 // openemr/library/classes/Prescription.class.php file.
15 // Decision was made in June 2013 that a sale line item in the Fee Sheet may
16 // come only from the specified warehouse. Set this to false if the decision
17 // is reversed.
18 $GLOBALS['SELL_FROM_ONE_WAREHOUSE'] = true;
20 $substitute_array = array('', xl('Allowed'), xl('Not Allowed'));
22 function send_drug_email($subject, $body)
24 $recipient = $GLOBALS['practice_return_email_path'];
25 if (empty($recipient)) {
26 return;
29 $mail = new PHPMailer();
30 $mail->From = $recipient;
31 $mail->FromName = 'In-House Pharmacy';
32 $mail->isMail();
33 $mail->Host = "localhost";
34 $mail->Mailer = "mail";
35 $mail->Body = $body;
36 $mail->Subject = $subject;
37 $mail->AddAddress($recipient);
38 if (!$mail->Send()) {
39 error_log("There has been a mail error sending to " . $recipient .
40 " " . $mail->ErrorInfo);
44 function sellDrug(
45 $drug_id,
46 $quantity,
47 $fee,
48 $patient_id = 0,
49 $encounter_id = 0,
50 $prescription_id = 0,
51 $sale_date = '',
52 $user = '',
53 $default_warehouse = '',
54 $testonly = false,
55 &$expiredlots = null,
56 $pricelevel = '',
57 $selector = ''
58 ) {
60 if (empty($patient_id)) {
61 $patient_id = $GLOBALS['pid'];
64 if (empty($sale_date)) {
65 $sale_date = date('Y-m-d');
68 if (empty($user)) {
69 $user = $_SESSION['authUser'];
72 // error_log("quantity = '$quantity'"); // debugging
74 if (empty($default_warehouse)) {
75 // Get the default warehouse, if any, for the user.
76 $rowuser = sqlQuery("SELECT default_warehouse FROM users WHERE username = ?", array($user));
77 $default_warehouse = $rowuser['default_warehouse'];
80 // Get relevant options for this product.
81 $rowdrug = sqlQuery("SELECT allow_combining, reorder_point, name, dispensable " .
82 "FROM drugs WHERE drug_id = ?", array($drug_id));
83 $allow_combining = $rowdrug['allow_combining'];
84 $dispensable = $rowdrug['dispensable'];
86 if (!$dispensable) {
87 // Non-dispensable is a much simpler case and does not touch inventory.
88 if ($testonly) {
89 return true;
92 $sale_id = sqlInsert(
93 "INSERT INTO drug_sales ( " .
94 "drug_id, inventory_id, prescription_id, pid, encounter, user, " .
95 "sale_date, quantity, fee ) VALUES ( " .
96 "?, 0, ?, ?, ?, ?, ?, ?, ?)",
97 array($drug_id, $prescription_id, $patient_id, $encounter_id, $user, $sale_date, $quantity, $fee)
99 return $sale_id;
102 // Combining is never allowed for prescriptions and will not work with
103 // dispense_drug.php.
104 if ($prescription_id) {
105 $allow_combining = 0;
108 $rows = array();
109 // $firstrow = false;
110 $qty_left = $quantity;
111 $bad_lot_list = '';
112 $total_on_hand = 0;
113 $gotexpired = false;
115 // If the user has a default warehouse, sort those lots first.
116 $orderby = ($default_warehouse === '') ?
117 "" : "di.warehouse_id != '$default_warehouse', ";
118 $orderby .= "lo.seq, di.expiration, di.lot_number, di.inventory_id";
120 // Retrieve lots in order of expiration date within warehouse preference.
121 $query = "SELECT di.*, lo.option_id, lo.seq " .
122 "FROM drug_inventory AS di " .
123 "LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " .
124 "lo.option_id = di.warehouse_id AND lo.activity = 1 " .
125 "WHERE " .
126 "di.drug_id = ? AND di.destroy_date IS NULL ";
127 $sqlarr = array($drug_id);
128 if ($GLOBALS['SELL_FROM_ONE_WAREHOUSE'] && $default_warehouse) {
129 $query .= "AND di.warehouse_id = ? ";
130 $sqlarr[] = $default_warehouse;
133 $query .= "ORDER BY $orderby";
134 $res = sqlStatement($query, $sqlarr);
136 // First pass. Pick out lots to be used in filling this order, figure out
137 // if there is enough quantity on hand and check for lots to be destroyed.
138 while ($row = sqlFetchArray($res)) {
139 if ($row['warehouse_id'] != $default_warehouse) {
140 // Warehouses with seq > 99 are not available.
141 $seq = empty($row['seq']) ? 0 : $row['seq'] + 0;
142 if ($seq > 99) {
143 continue;
147 $on_hand = $row['on_hand'];
148 $expired = (!empty($row['expiration']) && $row['expiration'] <= $sale_date);
149 if ($expired || $on_hand < $quantity) {
150 $tmp = $row['lot_number'];
151 if (! $tmp) {
152 $tmp = '[missing lot number]';
155 if ($bad_lot_list) {
156 $bad_lot_list .= ', ';
159 $bad_lot_list .= $tmp;
162 if ($expired) {
163 $gotexpired = true;
164 continue;
167 /*****************************************************************
168 // Note the first row in case total quantity is insufficient and we are
169 // allowed to go negative.
170 if (!$firstrow) $firstrow = $row;
171 *****************************************************************/
173 $total_on_hand += $on_hand;
175 if ($on_hand > 0 && $qty_left > 0 && ($allow_combining || $on_hand >= $qty_left)) {
176 $rows[] = $row;
177 $qty_left -= $on_hand;
181 if ($expiredlots !== null) {
182 $expiredlots = $gotexpired;
185 if ($testonly) {
186 // Just testing inventory, so return true if OK, false if insufficient.
187 // $qty_left, if positive, is the amount requested that could not be allocated.
188 return $qty_left <= 0;
191 if ($bad_lot_list) {
192 send_drug_email(
193 "Possible lot destruction needed",
194 "The following lot(s) are expired or were too small to fill the " .
195 "order for patient $patient_id: $bad_lot_list\n"
199 /*******************************************************************
200 if (empty($firstrow)) return 0; // no suitable lots exist
201 // This can happen when combining is not allowed. We will use the
202 // first row and take it negative.
203 if (empty($rows)) {
204 $rows[] = $firstrow;
205 $qty_left -= $firstrow['on_hand'];
207 *******************************************************************/
209 // The above was an experiment in permitting a negative lot quantity.
210 // We decided that was a bad idea, so now we just error out if there
211 // is not enough on hand.
212 if ($qty_left > 0) {
213 return 0;
216 $sale_id = 0;
217 $qty_final = $quantity; // remaining unallocated quantity
218 $fee_final = $fee; // remaining unallocated fee
220 // Second pass. Update the database.
221 foreach ($rows as $row) {
222 $inventory_id = $row['inventory_id'];
224 /*****************************************************************
225 $thisqty = $row['on_hand'];
226 if ($qty_left > 0) {
227 $thisqty += $qty_left;
228 $qty_left = 0;
230 else if ($thisqty > $qty_final) {
231 $thisqty = $qty_final;
233 *****************************************************************/
234 $thisqty = min($qty_final, $row['on_hand']);
236 $qty_final -= $thisqty;
238 // Compute the proportional fee for this line item. For the last line
239 // item take the remaining unallocated fee to avoid round-off error.
240 if ($qty_final) {
241 $thisfee = sprintf('%0.2f', $fee * $thisqty / $quantity);
242 } else {
243 $thisfee = sprintf('%0.2f', $fee_final);
246 $fee_final -= $thisfee;
248 // Update inventory and create the sale line item.
249 sqlStatement("UPDATE drug_inventory SET " .
250 "on_hand = on_hand - ? " .
251 "WHERE inventory_id = ?", array($thisqty,$inventory_id));
252 $sale_id = sqlInsert(
253 "INSERT INTO drug_sales ( " .
254 "drug_id, inventory_id, prescription_id, pid, encounter, user, sale_date, quantity, fee, pricelevel, selector ) " .
255 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
256 array($drug_id, $inventory_id, $prescription_id, $patient_id, $encounter_id, $user,
257 $sale_date,
258 $thisqty,
259 $thisfee,
260 $pricelevel,
261 $selector)
264 // If this sale exhausted the lot then auto-destroy it if that is wanted.
265 if ($row['on_hand'] == $thisqty && !empty($GLOBALS['gbl_auto_destroy_lots'])) {
266 sqlStatement(
267 "UPDATE drug_inventory SET " .
268 "destroy_date = ?, destroy_method = ?, destroy_witness = ?, destroy_notes = ? " .
269 "WHERE drug_id = ? AND inventory_id = ?",
270 array($sale_date, xl('Automatic from sale'), $user, "sale_id = $sale_id",
271 $drug_id,
272 $inventory_id)
277 /*******************************************************************
278 // If appropriate, generate email to notify that re-order is due.
279 if (($total_on_hand - $quantity) <= $rowdrug['reorder_point']) {
280 send_drug_email("Product re-order required",
281 "Product '" . $rowdrug['name'] . "' has reached its reorder point.\n");
283 // TBD: If the above is un-commented, fix it to handle the case of
284 // $GLOBALS['gbl_min_max_months'] being true.
285 *******************************************************************/
287 // If combining is allowed then $sale_id will be just the last inserted ID,
288 // and it serves only to indicate that everything worked. Otherwise there
289 // can be only one inserted row and this is its ID.
290 return $sale_id;
293 // Determine if facility and warehouse restrictions are applicable for this user.
294 function isUserRestricted($userid = 0)
296 if (!$userid) {
297 $userid = $_SESSION['authId'];
300 $countrow = sqlQuery("SELECT count(*) AS count FROM users_facility WHERE " .
301 "tablename = 'users' AND table_id = ?", array($userid));
302 return !empty($countrow['count']);
305 // Check if the user has access to the given facility.
306 // Do not call this if user is not restricted!
307 function isFacilityAllowed($facid, $userid = 0)
309 if (!$userid) {
310 $userid = $_SESSION['authId'];
313 $countrow = sqlQuery(
314 "SELECT count(*) AS count FROM users_facility WHERE " .
315 "tablename = 'users' AND table_id = ? AND facility_id = ?",
316 array($userid, $facid)
318 if (empty($countrow['count'])) {
319 $countrow = sqlQuery(
320 "SELECT count(*) AS count FROM users WHERE " .
321 "id = ? AND facility_id = ?",
322 array($userid, $facid)
324 return !empty($countrow['count']);
327 return true;
330 // Check if the user has access to the given warehouse within the given facility.
331 // Do not call this if user is not restricted!
332 function isWarehouseAllowed($facid, $whid, $userid = 0)
334 if (!$userid) {
335 $userid = $_SESSION['authId'];
338 $countrow = sqlQuery(
339 "SELECT count(*) AS count FROM users_facility WHERE " .
340 "tablename = 'users' AND table_id = ? AND facility_id = ? AND " .
341 "(warehouse_id = ? OR warehouse_id = '')",
342 array($userid, $facid, $whid)
344 if (empty($countrow['count'])) {
345 $countrow = sqlQuery(
346 "SELECT count(*) AS count FROM users WHERE " .
347 "id = ? AND default_warehouse = ?",
348 array($userid, $whid)
350 return !empty($countrow['count']);
353 return true;