5 * Product Name (blank where repeated)
6 * Warehouse Name (blank where repeated) or Total for Product
7 * Starting Inventory (detail lines: date)
8 * Ending Inventory (detail lines: invoice ID)
15 * @link http://www.open-emr.org
16 * @author Rod Roark <rod@sunsetsystems.com>
17 * @author Brady Miller <brady.g.miller@gmail.com>
18 * @copyright Copyright (c) 2010-2016 Rod Roark <rod@sunsetsystems.com>
19 * @copyright Copyright (c) 2017-2019 Brady Miller <brady.g.miller@gmail.com>
20 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
23 require_once("../globals.php");
24 require_once("$srcdir/patient.inc.php");
26 use OpenEMR\Common\Acl\AclMain
;
27 use OpenEMR\Common\Csrf\CsrfUtils
;
28 use OpenEMR\Common\Twig\TwigContainer
;
29 use OpenEMR\Core\Header
;
32 if (!CsrfUtils
::verifyCsrfToken($_POST["csrf_token_form"])) {
33 CsrfUtils
::csrfNotVerified();
37 // Specify if product or warehouse is the first column.
38 $product_first = (!empty($_POST['form_by']) && $_POST['form_by'] == 'w') ?
0 : 1;
40 $last_warehouse_id = '~';
43 // Get ending inventory for the report's end date.
44 // Optionally restricts by product ID and/or warehouse ID.
45 function getEndInventory($product_id = 0, $warehouse_id = '~')
47 global $form_from_date, $form_to_date, $form_product;
50 if ($warehouse_id !== '~') {
51 $whidcond = $warehouse_id === '' ?
52 "AND ( di.warehouse_id IS NULL OR di.warehouse_id = '' )" :
53 "AND di.warehouse_id = '" . add_escape_custom($warehouse_id) . "'";
58 $product_id = $form_product;
62 $prodcond = "AND di.drug_id = '" . add_escape_custom($product_id) . "'";
65 // Get sum of current inventory quantities + destructions done after the
66 // report end date (which is effectively a type of transaction).
67 $eirow = sqlQuery("SELECT sum(di.on_hand) AS on_hand " .
68 "FROM drug_inventory AS di WHERE " .
69 "( di.destroy_date IS NULL OR di.destroy_date > ? ) " .
70 "$prodcond $whidcond", array($form_to_date));
72 // Get sum of sales/adjustments/purchases after the report end date.
73 $sarow = sqlQuery("SELECT sum(ds.quantity) AS quantity " .
74 "FROM drug_sales AS ds, drug_inventory AS di WHERE " .
75 "ds.sale_date > ? AND " .
76 "di.inventory_id = ds.inventory_id " .
77 "$prodcond $whidcond", array($form_to_date));
79 // Get sum of transfers out after the report end date.
80 $xfrow = sqlQuery("SELECT sum(ds.quantity) AS quantity " .
81 "FROM drug_sales AS ds, drug_inventory AS di WHERE " .
82 "ds.sale_date > ? AND " .
83 "di.inventory_id = ds.xfer_inventory_id " .
84 "$prodcond $whidcond", array($form_to_date));
86 return $eirow['on_hand'] +
$sarow['quantity'] - $xfrow['quantity'];
89 function thisLineItem(
101 global $warehouse, $product, $secqtys, $priqtys, $grandqtys;
102 global $whleft, $prodleft; // left 2 columns, blank where repeated
103 global $last_warehouse_id, $last_product_id, $product_first;
106 $invnumber = empty($irnumber) ?
($patient_id ?
"$patient_id.$encounter_id" : "") : $irnumber;
108 // Product name for this detail line item.
109 if (empty($rowprod)) {
110 $rowprod = 'Unnamed Product';
113 // Warehouse name for this line item.
118 // If new warehouse or product...
119 if ($warehouse_id != $last_warehouse_id ||
$product_id != $last_product_id) {
120 // If there was anything to total...
121 if (($product_first && $last_warehouse_id != '~') ||
(!$product_first && $last_product_id)) {
122 $secei = getEndInventory($last_product_id, $last_warehouse_id);
124 // Print second-column totals.
125 if ($form_action == 'export') {
127 if (! $_POST['form_details']) {
128 if ($product_first) {
129 echo csvEscape($product);
130 echo ',' . csvEscape($warehouse);
132 echo csvEscape($warehouse);
133 echo ',' . csvEscape($product);
136 echo ',' . csvEscape($secei - $secqtys[0] - $secqtys[1] - $secqtys[2] - $secqtys[3] - $secqtys[4]); // start inventory
137 echo ',' . csvEscape($secqtys[0]); // sales
138 echo ',' . csvEscape($secqtys[1]); // distributions
139 echo ',' . csvEscape($secqtys[2]); // purchases
140 echo ',' . csvEscape($secqtys[3]); // transfers
141 echo ',' . csvEscape($secqtys[4]); // adjustments
142 echo ',' . csvEscape($secei); // end inventory
148 <tr bgcolor
="#ddddff">
149 <?php
if ($product_first) { ?
>
151 <?php
echo text($prodleft);
154 <td
class="detail" colspan
='3'>
156 if ($_POST['form_details']) {
157 echo xlt('Total for') . ' ';
159 echo text($warehouse); ?
>
163 <?php
echo text($whleft);
166 <td
class="detail" colspan
='3'>
168 if ($_POST['form_details']) {
169 echo xlt('Total for') . ' ';
171 echo text($product); ?
>
174 <td
class="dehead" align
="right">
175 <?php
echo text($secei - $secqtys[0] - $secqtys[1] - $secqtys[2] - $secqtys[3] - $secqtys[4]); ?
>
177 <td
class="dehead" align
="right">
178 <?php
echo text($secqtys[0]); ?
>
180 <td
class="dehead" align
="right">
181 <?php
echo text($secqtys[1]); ?
>
183 <td
class="dehead" align
="right">
184 <?php
echo text($secqtys[2]); ?
>
186 <td
class="dehead" align
="right">
187 <?php
echo text($secqtys[3]); ?
>
189 <td
class="dehead" align
="right">
190 <?php
echo text($secqtys[4]); ?
>
192 <td
class="dehead" align
="right">
193 <?php
echo text($secei); ?
>
197 } // End not csv export
200 $secqtys = array(0, 0, 0, 0, 0);
201 if ($product_first) {
202 $whleft = $warehouse = $rowwh;
203 $last_warehouse_id = $warehouse_id;
205 $prodleft = $product = $rowprod;
206 $last_product_id = $product_id;
210 // If first column is changing, time for its totals.
212 ($product_first && $product_id != $last_product_id) ||
213 (!$product_first && $warehouse_id != $last_warehouse_id)
216 ($product_first && $last_product_id) ||
217 (!$product_first && $last_warehouse_id != '~')
219 $priei = $product_first ?
getEndInventory($last_product_id) :
220 getEndInventory(0, $last_warehouse_id);
221 // Print first column total.
222 if ($form_action != 'export') {
225 <tr bgcolor
="#ffdddd">
229 <td
class="detail" colspan
="3">
230 <?php
echo xlt('Total for') . ' ';
231 echo text($product_first ?
$product : $warehouse); ?
>
233 <td
class="dehead" align
="right">
234 <?php
echo text($priei - $priqtys[0] - $priqtys[1] - $priqtys[2] - $priqtys[3] - $priqtys[4]); ?
>
236 <td
class="dehead" align
="right">
237 <?php
echo text($priqtys[0]); ?
>
239 <td
class="dehead" align
="right">
240 <?php
echo text($priqtys[1]); ?
>
242 <td
class="dehead" align
="right">
243 <?php
echo text($priqtys[2]); ?
>
245 <td
class="dehead" align
="right">
246 <?php
echo text($priqtys[3]); ?
>
248 <td
class="dehead" align
="right">
249 <?php
echo text($priqtys[4]); ?
>
251 <td
class="dehead" align
="right">
252 <?php
echo text($priei); ?
>
256 } // End not csv export
259 $priqtys = array(0, 0, 0, 0, 0);
260 if ($product_first) {
261 $prodleft = $product = $rowprod;
262 $last_product_id = $product_id;
264 $whleft = $warehouse = $rowwh;
265 $last_warehouse_id = $warehouse_id;
270 if ($_POST['form_details'] && $product_id && ($qtys[0] +
$qtys[1] +
$qtys[2] +
$qtys[3] +
$qtys[4])) {
271 if ($form_action == 'export') {
272 if ($product_first) {
273 echo csvEscape($product);
274 echo ',' . csvEscape($warehouse);
276 echo csvEscape($warehouse);
277 echo ',' . csvEscape($product);
280 echo ',' . csvEscape(oeFormatShortDate($transdate));
281 echo ',' . csvEscape($invnumber);
282 echo ',' . csvEscape($qtys[0]); // sales
283 echo ',' . csvEscape($qtys[1]); // distributions
284 echo ',' . csvEscape($qtys[2]); // purchases
285 echo ',' . csvEscape($qtys[3]); // transfers
286 echo ',' . csvEscape($qtys[4]); // adjustments
291 <?php
if ($product_first) { ?
>
293 <?php
echo text($prodleft);
297 <?php
echo text($whleft);
302 <?php
echo text($whleft);
306 <?php
echo text($prodleft);
311 <?php
echo text(oeFormatShortDate($transdate)); ?
>
314 <?php
echo text($invnumber); ?
>
319 <td
class="dehead" align
="right">
320 <?php
echo text($qtys[0]); ?
>
322 <td
class="dehead" align
="right">
323 <?php
echo text($qtys[1]); ?
>
325 <td
class="dehead" align
="right">
326 <?php
echo text($qtys[2]); ?
>
328 <td
class="dehead" align
="right">
329 <?php
echo text($qtys[3]); ?
>
331 <td
class="dehead" align
="right">
332 <?php
echo text($qtys[4]); ?
>
339 } // End not csv export
341 for ($i = 0; $i < 5; ++
$i) {
342 $secqtys[$i] +
= $qtys[$i];
343 $priqtys[$i] +
= $qtys[$i];
344 $grandqtys[$i] +
= $qtys[$i];
348 if (! AclMain
::aclCheckCore('acct', 'rep')) {
349 echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Inventory Activity")]);
353 // this is "" or "submit" or "export".
354 $form_action = $_POST['form_action'];
356 $form_from_date = (isset($_POST['form_from_date'])) ?
DateToYYYYMMDD($_POST['form_from_date']) : date('Y-m-d');
357 $form_to_date = (isset($_POST['form_to_date'])) ?
DateToYYYYMMDD($_POST['form_to_date']) : date('Y-m-d');
358 $form_product = $_POST['form_product'];
360 if ($form_action == 'export') {
361 header("Pragma: public");
362 header("Expires: 0");
363 header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
364 header("Content-Type: application/force-download");
365 header("Content-Disposition: attachment; filename=inventory_activity.csv");
366 header("Content-Description: File Transfer");
368 if ($product_first) {
369 echo csvEscape(xl('Product')) . ',';
370 echo csvEscape(xl('Warehouse')) . ',';
372 echo csvEscape(xl('Warehouse')) . ',';
373 echo csvEscape(xl('Product')) . ',';
376 if ($_POST['form_details']) {
377 echo csvEscape(xl('Date')) . ',';
378 echo csvEscape(xl('Invoice')) . ',';
379 echo csvEscape(xl('Sales')) . ',';
380 echo csvEscape(xl('Distributions')) . ',';
381 echo csvEscape(xl('Purchases')) . ',';
382 echo csvEscape(xl('Transfers')) . ',';
383 echo csvEscape(xl('Adjustments')) . "\n";
385 echo csvEscape(xl('Start')) . ',';
386 echo csvEscape(xl('Sales')) . ',';
387 echo csvEscape(xl('Distributions')) . ',';
388 echo csvEscape(xl('Purchases')) . ',';
389 echo csvEscape(xl('Transfers')) . ',';
390 echo csvEscape(xl('Adjustments')) . ',';
391 echo csvEscape(xl('End')) . "\n";
393 } else { // end export
397 <title
><?php
echo xlt('Inventory Activity'); ?
></title
>
399 <?php Header
::setupHeader(['datetime-picker', 'report-helper']); ?
>
402 /* specifically include & exclude from printing */
404 #report_parameters {visibility: hidden; display: none;}
405 #report_parameters_daterange {visibility: visible; display: inline;}
406 #report_results {margin-top: 30px;}
408 /* specifically exclude some from the screen */
410 #report_parameters_daterange {visibility: hidden; display: none;}
412 body
{ font
-family
:sans
-serif
; font
-size
:10pt
; font
-weight
:normal
}
413 .dehead
{ color
:var(--black
); font
-family
:sans
-serif
; font
-size
:10pt
; font
-weight
:bold
}
414 .detail
{ color
:var(--black
); font
-family
:sans
-serif
; font
-size
:10pt
; font
-weight
:normal
}
416 table
.mymaintable
, table
.mymaintable td
, table
.mymaintable th
{
417 border
: 1px solid
#aaaaaa;
418 border
-collapse
: collapse
;
420 table
.mymaintable td
, table
.mymaintable th
{
421 padding
: 1pt
4pt
1pt
4pt
;
428 oeFixedHeaderSetup(document
.getElementById('mymaintable'));
429 var win
= top
.printLogSetup ? top
: opener
.top
;
430 win
.printLogSetup(document
.getElementById('printbutton'));
432 $
('.datepicker').datetimepicker({
433 <?php
$datetimepicker_timepicker = false; ?
>
434 <?php
$datetimepicker_showseconds = false; ?
>
435 <?php
$datetimepicker_formatInput = true; ?
>
436 <?php
require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?
>
437 <?php
// can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>
441 function mysubmit(action
) {
442 var f
= document
.forms
[0];
443 f
.form_action
.value
= action
;
444 top
.restoreSession();
452 <body leftmargin
='0' topmargin
='0' marginwidth
='0' marginheight
='0' class='body_top'>
456 <h2
><?php
echo xlt('Inventory Activity'); ?
></h2
>
458 <form method
='post' action
='inventory_activity.php?product=<?php echo attr_url($product_first); ?>' onsubmit
='return top.restoreSession()'>
459 <input type
="hidden" name
="csrf_token_form" value
="<?php echo attr(CsrfUtils::collectCsrfToken()); ?>" />
461 <div id
="report_parameters">
462 <!-- form_action is set to
"submit" or "export" at form submit time
-->
463 <input type
='hidden' name
='form_action' value
='' />
469 <td
class='label_custom'>
470 <?php
echo xlt('By'); ?
>:
473 <select name
='form_by'>
474 <option value
='p'><?php
echo xlt('Product'); ?
></option
>
475 <option value
='w'<?php
echo (!$product_first) ?
' selected' : ''; ?
>><?php
echo xlt('Warehouse'); ?
></option
>
478 <td
class='label_custom'>
479 <?php
echo xlt('From'); ?
>:
482 <input type
='text' class='datepicker' name
='form_from_date' id
="form_from_date" size
='10'
483 value
='<?php echo attr(oeFormatShortDate($form_from_date)); ?>'>
485 <td
class='label_custom'>
486 <?php
echo xlt('To{{Range}}'); ?
>:
489 <input type
='text' class='datepicker' name
='form_to_date' id
="form_to_date" size
='10'
490 value
='<?php echo attr(oeFormatShortDate($form_to_date)); ?>'>
494 <td
class='label_custom'>
495 <?php
echo xlt('For'); ?
>:
499 // Build a drop-down list of products.
501 $query = "SELECT drug_id, name FROM drugs ORDER BY name, drug_id";
502 $pres = sqlStatement($query);
503 echo " <select name='form_product'>\n";
504 echo " <option value=''>-- " . xlt('All Products') . " --\n";
505 while ($prow = sqlFetchArray($pres)) {
506 $drug_id = $prow['drug_id'];
507 echo " <option value='" . attr($drug_id) . "'";
508 if ($drug_id == $form_product) {
512 echo ">" . text($prow['name']) . "\n";
518 <td
class='label_custom'>
519 <?php
echo xlt('Details'); ?
>:
521 <td colspan
='3' nowrap
>
522 <input type
='checkbox' name
='form_details' value
='1'<?php
echo ($_POST['form_details']) ?
" checked" : "";?
> />
527 <td align
='left' valign
='middle'>
528 <table
class='w-100 h-100' style
='border-left:1px solid;'>
531 <a href
='#' class='btn btn-primary' onclick
='mysubmit("submit")' style
='margin-left:1em'>
532 <span
><?php
echo xlt('Submit'); ?
></span
>
534 <?php
if ($form_action) { ?
>
535 <a href
='#' class='btn btn-primary' id
='printbutton' style
='margin-left:1em'>
536 <span
><?php
echo xlt('Print'); ?
></span
>
538 <a href
='#' class='btn btn-primary' onclick
='mysubmit("export")' style
='margin-left:1em'>
539 <span
><?php
echo xlt('CSV Export'); ?
></span
>
550 <?php
if ($form_action) { // if submit (already not export here) ?>
551 <div id
="report_results">
552 <table width
='98%' id
='mymaintable' class='mymaintable'>
554 <tr bgcolor
="#dddddd">
556 <?php
echo text($product_first ?
xl('Product') : xl('Warehouse')); ?
>
558 <?php
if ($_POST['form_details']) { ?
>
560 <?php
echo text($product_first ?
xl('Warehouse') : xl('Product')); ?
>
563 <?php
echo xlt('Date'); ?
>
566 <?php
echo xlt('Invoice'); ?
>
569 <td
class="dehead" colspan
="3">
570 <?php
echo text($product_first ?
xl('Warehouse') : xl('Product')); ?
>
573 <td
class="dehead" align
="right" width
="8%">
574 <?php
echo xlt('Start'); ?
>
576 <td
class="dehead" align
="right" width
="8%">
577 <?php
echo xlt('Sales'); ?
>
579 <td
class="dehead" align
="right" width
="8%">
580 <?php
echo xlt('Distributions'); ?
>
582 <td
class="dehead" align
="right" width
="8%">
583 <?php
echo xlt('Purchases'); ?
>
585 <td
class="dehead" align
="right" width
="8%">
586 <?php
echo xlt('Transfers'); ?
>
588 <td
class="dehead" align
="right" width
="8%">
589 <?php
echo xlt('Adjustments'); ?
>
591 <td
class="dehead" align
="right" width
="8%">
592 <?php
echo xlt('End'); ?
>
601 if ($form_action) { // if submit or export
602 $from_date = $form_from_date;
603 $to_date = $form_to_date;
609 $grandqtys = array(0, 0, 0, 0, 0);
610 $priqtys = array(0, 0, 0, 0, 0);
611 $secqtys = array(0, 0, 0, 0, 0);
612 $last_inventory_id = 0;
614 $sqlBindArray = array();
616 $query = "SELECT s.sale_id, s.sale_date, s.quantity, s.fee, s.pid, s.encounter, " .
617 "s.xfer_inventory_id, s.distributor_id, d.name, lo.title, " .
618 "di.drug_id, di.warehouse_id, di.inventory_id, di.destroy_date, di.on_hand, " .
619 "fe.invoice_refno " .
620 "FROM drug_inventory AS di " .
621 "JOIN drugs AS d ON d.drug_id = di.drug_id " .
622 "LEFT JOIN drug_sales AS s ON " .
623 "s.sale_date >= ? AND s.sale_date <= ? AND " .
624 "s.drug_id = di.drug_id AND " .
625 "( s.inventory_id = di.inventory_id OR s.xfer_inventory_id = di.inventory_id ) " .
626 "LEFT JOIN list_options AS lo ON lo.list_id = 'warehouse' AND " .
627 "lo.option_id = di.warehouse_id AND lo.activity = 1 " .
628 "LEFT JOIN form_encounter AS fe ON fe.pid = s.pid AND fe.encounter = s.encounter " .
629 "WHERE ( di.destroy_date IS NULL OR di.destroy_date >= ? ) AND " .
630 "( di.on_hand != 0 OR s.sale_id IS NOT NULL )";
632 array_push($sqlBindArray, $from_date, $to_date, $form_from_date);
634 // If a product was specified.
636 $query .= " AND di.drug_id = ?";
637 array_push($sqlBindArray, $form_product);
640 if ($product_first) {
641 $query .= " ORDER BY d.name, d.drug_id, lo.title, di.warehouse_id, " .
642 "di.inventory_id, s.sale_date, s.sale_id";
644 $query .= " ORDER BY lo.title, di.warehouse_id, d.name, d.drug_id, " .
645 "di.inventory_id, s.sale_date, s.sale_id";
648 $res = sqlStatement($query, $sqlBindArray);
649 while ($row = sqlFetchArray($res)) {
650 // If new lot and it was destroyed during the reporting period,
651 // generate a pseudo-adjustment for that.
652 if ($row['inventory_id'] != $last_inventory_id) {
653 $last_inventory_id = $row['inventory_id'];
655 !empty($row['destroy_date']) && $row['on_hand'] != 0
656 && $row['destroy_date'] <= $form_to_date
660 $row['warehouse_id'],
665 $row['destroy_date'],
666 array(0, 0, 0, 0, 0 - $row['on_hand']),
672 $qtys = array(0, 0, 0, 0, 0);
673 if ($row['sale_id']) {
674 if ($row['xfer_inventory_id']) {
675 // A transfer sale item will appear twice, once with each lot.
676 if ($row['inventory_id'] == $row['xfer_inventory_id']) {
677 $qtys[3] = $row['quantity'];
679 $qtys[3] = 0 - $row['quantity'];
681 } elseif ($row['pid']) {
682 $qtys[0] = 0 - $row['quantity'];
683 } elseif ($row['distributor_id']) {
684 $qtys[1] = 0 - $row['quantity'];
685 } elseif ($row['fee'] != 0) {
686 $qtys[2] = 0 - $row['quantity'];
687 } else { // no pid, distributor, source lot or fee: must be an adjustment
688 $qtys[4] = 0 - $row['quantity'];
694 $row['warehouse_id'],
696 $row['encounter'] +
0,
701 $row['invoice_refno']
705 // Generate totals for last product and warehouse.
706 thisLineItem(0, '~', 0, 0, '', '', '0000-00-00', array(0, 0, 0, 0, 0));
708 // Grand totals line.
709 if ($form_action != 'export') { // if submit
710 $grei = getEndInventory();
712 <tr bgcolor
="#dddddd">
713 <td
class="detail" colspan
="4">
714 <?php
echo xlt('Grand Total'); ?
>
716 <td
class="dehead" align
="right">
717 <?php
echo text($grei - $grandqtys[0] - $grandqtys[1] - $grandqtys[2] - $grandqtys[3] - $grandqtys[4]); ?
>
719 <td
class="dehead" align
="right">
720 <?php
echo text($grandqtys[0]); ?
>
722 <td
class="dehead" align
="right">
723 <?php
echo text($grandqtys[1]); ?
>
725 <td
class="dehead" align
="right">
726 <?php
echo text($grandqtys[2]); ?
>
728 <td
class="dehead" align
="right">
729 <?php
echo text($grandqtys[3]); ?
>
731 <td
class="dehead" align
="right">
732 <?php
echo text($grandqtys[4]); ?
>
734 <td
class="dehead" align
="right">
735 <?php
echo text($grei); ?
>
740 } // end if submit or export
742 if ($form_action != 'export') {
749 } // end if ($form_action)