dev ldap fixes (#3914)
[openemr.git] / library / FeeSheetHtml.class.php
blob63bbe943a8366495eef5a9ec2a39be7a65beeba9
1 <?php
3 /**
4 * library/FeeSheetHtml.class.php
6 * Class for HTML-specific implementations of the Fee Sheet.
8 * @package OpenEMR
9 * @link https://www.open-emr.org
10 * @author Rod Roark <rod@sunsetsystems.com>
11 * @author Brady Miller <brady.g.miller@gmail.com>
12 * @copyright Copyright (c) 2019 Brady Miller <brady.g.miller@gmail.com>
13 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
16 require_once(dirname(__FILE__) . "/FeeSheet.class.php");
17 require_once(dirname(__FILE__) . "/api.inc");
19 class FeeSheetHtml extends FeeSheet
22 // Dynamically generated JavaScript to maintain justification codes.
23 public $justinit = "var f = document.forms[0];\n";
25 function __construct($pid = 0, $encounter = 0)
27 parent::__construct($pid, $encounter);
30 // Build a drop-down list of providers. This includes users who
31 // have the word "provider" anywhere in their "additional info"
32 // field, so that we can define providers (for billing purposes)
33 // who do not appear in the calendar.
35 public static function genProviderOptionList($toptext, $default = 0)
37 $s = '';
38 // Get user's default facility, or 0 if none.
39 $drow = sqlQuery("SELECT facility_id FROM users where username = ?", [$_SESSION['authUser']]);
40 $def_facility = 0 + $drow['facility_id'];
42 $sqlarr = array($def_facility);
43 $query = "SELECT id, lname, fname, facility_id FROM users WHERE " .
44 "( authorized = 1 OR info LIKE '%provider%' ) AND username != '' " .
45 "AND active = 1 AND ( info IS NULL OR info NOT LIKE '%Inactive%' )";
46 // If restricting to providers matching user facility...
47 if ($GLOBALS['gbl_restrict_provider_facility']) {
48 $query .= " AND ( facility_id = 0 OR facility_id = ? )";
49 $query .= " ORDER BY lname, fname";
50 } else { // If not restricting then sort the matching providers first.
51 $query .= " ORDER BY (facility_id = ?) DESC, lname, fname";
54 $res = sqlStatement($query, $sqlarr);
55 $s .= "<option value=''>" . text($toptext) . "</option>";
56 while ($row = sqlFetchArray($res)) {
57 $provid = $row['id'];
58 $s .= "<option value='" . attr($provid) . "'";
59 if ($provid == $default) {
60 $s .= " selected";
63 $s .= ">";
64 if (!$GLOBALS['gbl_restrict_provider_facility'] && $def_facility && $row['facility_id'] == $def_facility) {
65 // Mark providers in the matching facility with an asterisk.
66 $s .= "* ";
69 $s .= text($row['lname'] . ", " . $row['fname']) . "</option>";
72 return $s;
75 // Does the above but including <select> ... </select>.
77 public static function genProviderSelect($tagname, $toptext, $default = 0, $disabled = false)
79 $s = " <span class='form-inline'><select class='form-control' name='" . attr($tagname) . "'";
80 if ($disabled) {
81 $s .= " disabled";
84 $s .= ">";
85 $s .= self::genProviderOptionList($toptext, $default);
86 $s .= "</select></span>\n";
87 return $s;
90 // Build a drop-down list of warehouses.
92 public function genWarehouseSelect($tagname, $toptext, $default = '', $disabled = false, $drug_id = 0, $is_sold = 0)
94 $s = '';
95 if ($this->got_warehouses) {
96 // Normally would use generate_select_list() but it's not flexible enough here.
97 $s .= "<span class='form-inline'><select class='form-control' name='" . attr($tagname) . "'";
98 if (!$disabled) {
99 $s .= " onchange='warehouse_changed(this);'";
102 if ($disabled) {
103 $s .= " disabled";
106 $s .= ">";
107 $s .= "<option value=''>" . text($toptext) . "</option>";
108 $lres = sqlStatement("SELECT * FROM list_options " .
109 "WHERE list_id = 'warehouse' AND activity = 1 ORDER BY seq, title");
110 while ($lrow = sqlFetchArray($lres)) {
111 $s .= "<option value='" . attr($lrow['option_id']) . "'";
112 if ($disabled) {
113 if ($lrow['option_id'] == $default) {
114 $s .= " selected";
116 } else {
117 $has_inventory = sellDrug($drug_id, 1, 0, 0, 0, 0, '', '', $lrow['option_id'], true);
118 if (
119 ((strlen($default) == 0 && $lrow['is_default']) ||
120 (strlen($default) > 0 && $lrow['option_id'] == $default)) &&
121 ($is_sold || $has_inventory)
123 $s .= " selected";
124 } else {
125 // Disable this warehouse option if not selected and has no inventory.
126 if (!$has_inventory) {
127 $s .= " disabled";
132 $s .= ">" . text(xl_list_label($lrow['title'])) . "</option>\n";
135 $s .= "</select></span>";
138 return $s;
141 // Build a drop-down list of price levels.
142 // Includes the specified item's price in the "id" of each option.
144 public function genPriceLevelSelect($tagname, $toptext, $pr_id, $pr_selector = '', $default = '', $disabled = false)
146 // echo "<!-- pr_id = '$pr_id', pr_selector = '$pr_selector' -->\n"; // debugging
147 $s = "<span class='form-inline'><select class='form-control' name='" . attr($tagname) . "'";
148 if (!$disabled) {
149 $s .= " onchange='pricelevel_changed(this);'";
152 if ($disabled) {
153 $s .= " disabled";
156 $s .= ">";
157 $s .= "<option value=''>" . text($toptext) . "</option>";
158 $lres = sqlStatement(
159 "SELECT lo.*, p.pr_price " .
160 "FROM list_options AS lo " .
161 "LEFT JOIN prices AS p ON p.pr_id = ? AND p.pr_selector = ? AND p.pr_level = lo.option_id " .
162 "WHERE lo.list_id = 'pricelevel' AND lo.activity = 1 ORDER BY lo.seq, lo.title",
163 array($pr_id, $pr_selector)
165 $standardPrice = 0;
166 while ($lrow = sqlFetchArray($lres)) {
167 $price = empty($lrow['pr_price']) ? 0 : $lrow['pr_price'];
169 // if percent-based pricing is enabled...
170 if ($GLOBALS['enable_percent_pricing']) {
171 // Set standardPrice as the first price level (sorted by seq)
172 if ($standardPrice === 0) {
173 $standardPrice = $price;
176 // If price level notes contains a percentage,
177 // calculate price as percentage of standard price
178 $notes = $lrow['notes'];
179 if (!empty($notes) && strpos($notes, '%') > -1) {
180 $percent = intval(str_replace('%', '', $notes));
181 if ($percent > 0) {
182 $price = $standardPrice * ((100 - $percent) / 100);
187 $s .= "<option value='" . attr($lrow['option_id']) . "'";
188 $s .= " id='prc_$price'";
189 if (
190 (strlen($default) == 0 && $lrow['is_default'] && !$disabled) ||
191 (strlen($default) > 0 && $lrow['option_id'] == $default)
193 $s .= " selected";
196 $s .= ">" . text(xl_list_label($lrow['title'])) . "</option>\n";
199 $s .= "</select></span>";
200 return $s;
203 // If Contraception forms can be auto-created by the Fee Sheet we might need
204 // to ask about the client's prior contraceptive use.
206 public function generateContraceptionSelector($tagname = 'newmauser')
208 $s = '';
209 if ($GLOBALS['gbl_new_acceptor_policy'] == '1') {
210 $csrow = sqlQuery(
211 "SELECT COUNT(*) AS count FROM forms AS f WHERE " .
212 "f.pid = ? AND f.encounter = ? AND " .
213 "f.formdir = 'LBFccicon' AND f.deleted = 0",
214 array($this->pid, $this->encounter)
216 // Do it only if a contraception form does not already exist for this visit.
217 // Otherwise assume that whoever created it knows what they were doing.
218 if ($csrow['count'] == 0) {
219 // Determine if this client ever started contraception with the MA.
220 // Even if only a method change, we assume they have.
221 $query = "SELECT f.form_id FROM forms AS f " .
222 "JOIN form_encounter AS fe ON fe.pid = f.pid AND fe.encounter = f.encounter " .
223 "WHERE f.formdir = 'LBFccicon' AND f.deleted = 0 AND f.pid = ? " .
224 "ORDER BY fe.date DESC LIMIT 1";
225 $csrow = sqlQuery($query, array($this->pid));
226 if (empty($csrow)) {
227 $s .= "<span class='form-inline'><select class='form-control' name='$tagname'>\n";
228 $s .= " <option value='2'>" . xlt('First Modern Contraceptive Use (Lifetime)') . "</option>\n";
229 $s .= " <option value='1'>" . xlt('First Modern Contraception at this Clinic (with Prior Contraceptive Use)') . "</option>\n";
230 $s .= " <option value='0'>" . xlt('Method Change at this Clinic') . "</option>\n";
231 $s .= "</select></span>\n";
236 return $s;
239 // Generate a price level drop-down defaulting to the patient's current price level.
241 public function generatePriceLevelSelector($tagname = 'pricelevel', $disabled = false)
243 $s = "<span class='form-inline'><select class='form-control' name='" . attr($tagname) . "'";
244 if ($disabled) {
245 $s .= " disabled";
248 $s .= ">";
249 $pricelevel = $this->getPriceLevel();
250 $plres = sqlStatement("SELECT option_id, title FROM list_options " .
251 "WHERE list_id = 'pricelevel' AND activity = 1 ORDER BY seq");
252 while ($plrow = sqlFetchArray($plres)) {
253 $key = $plrow['option_id'];
254 $val = $plrow['title'];
255 $s .= "<option value='" . attr($key) . "'";
256 if ($key == $pricelevel) {
257 $s .= ' selected';
260 $s .= ">" . text(xl_list_label($val)) . "</option>";
263 $s .= "</select></span>";
264 return $s;
267 // Return Javascript that defines a function to validate the line items.
268 // Most of this is currently IPPF-specific, but NDC codes are also validated.
269 // This also computes and sets the form's ippfconmeth value if appropriate.
270 // This does not validate form fields not related to or derived from line items.
271 // Do not call this javascript function if you are just refreshing the form.
272 // The arguments are the names of the form arrays for services and products.
274 public function jsLineItemValidation($bill = 'bill', $prod = 'prod')
276 $s = "
277 function jsLineItemValidation(f) {
278 var max_contra_cyp = 0;
279 var max_contra_code = '';
280 var required_code_count = 0;
281 // Loop thru the services.
282 for (var lino = 0; f['{$bill}['+lino+'][code_type]']; ++lino) {
283 var pfx = '{$bill}[' + lino + ']';
284 if (f[pfx + '[del]'] && f[pfx + '[del]'].checked) continue;
285 if (f[pfx + '[ndcnum]'] && f[pfx + '[ndcnum]'].value) {
286 // Check NDC number format.
287 var ndcok = true;
288 var ndc = f[pfx + '[ndcnum]'].value;
289 var a = ndc.split('-');
290 if (a.length != 3) {
291 ndcok = false;
293 else if (a[0].length < 1 || a[1].length < 1 || a[2].length < 1 ||
294 a[0].length > 5 || a[1].length > 4 || a[2].length > 2) {
295 ndcok = false;
297 else {
298 for (var i = 0; i < 3; ++i) {
299 for (var j = 0; j < a[i].length; ++j) {
300 var c = a[i].charAt(j);
301 if (c < '0' || c > '9') ndcok = false;
305 if (!ndcok) {
306 alert('" . xls('Format incorrect for NDC') . "\"' + ndc +
307 '\", " . xls('should be like nnnnn-nnnn-nn') . "');
308 if (f[pfx+'[ndcnum]'].focus) f[pfx+'[ndcnum]'].focus();
309 return false;
311 // Check for valid quantity.
312 var qty = f[pfx+'[ndcqty]'].value - 0;
313 if (isNaN(qty) || qty <= 0) {
314 alert('" . xls('Quantity for NDC') . " \"' + ndc +
315 '\" " . xls('is not valid (decimal fractions are OK).') . "');
316 if (f[pfx+'[ndcqty]'].focus) f[pfx+'[ndcqty]'].focus();
317 return false;
320 if (f[pfx+'[method]'] && f[pfx+'[method]'].value) {
321 // The following applies to contraception for family planning clinics.
322 var tmp_cyp = parseFloat(f[pfx+'[cyp]'].value);
323 var tmp_meth = f[pfx+'[method]'].value;
324 var tmp_methtype = parseInt(f[pfx+'[methtype]'].value);
325 if (tmp_cyp > max_contra_cyp && tmp_methtype == 2) {
326 // max_contra_* tracks max cyp for initial consults only.
327 max_contra_cyp = tmp_cyp;
328 max_contra_code = tmp_meth;
331 if ($this->patient_male) {
332 $s .= "
333 var male_compatible_method = (
334 // TBD: Fix hard coded dependency on IPPFCM codes here.
335 tmp_meth == '4450' || // male condoms
336 tmp_meth == '4570'); // male vasectomy
337 if (!male_compatible_method) {
338 if (!confirm('" . xls('Warning: Contraceptive method is not compatible with a male patient.') . "'))
339 return false;
342 } // end if male patient
343 if ($this->patient_age < 10 || $this->patient_age > 50) {
344 $s .= "
345 if (!confirm('" . xls('Warning: Contraception for a patient under 10 or over 50.') . "'))
346 return false;
348 } // end if improper age
349 if ($this->match_services_to_products) {
350 $s .= "
351 // Nonsurgical methods should normally include a corresponding product.
352 // This takes advantage of the fact that only nonsurgical methods have CYP
353 // less than 10, in both the old and new frameworks.
354 if (tmp_cyp < 10.0) {
355 // Was: if (tmp_meth.substring(0, 2) != '12') {
356 var got_prod = false;
357 for (var plino = 0; f['{$prod}['+plino+'][drug_id]']; ++plino) {
358 var ppfx = '{$prod}[' + plino + ']';
359 if (f[ppfx+'[del]'] && f[ppfx+'[del]'].checked) continue;
360 if (f[ppfx+'[method]'] && f[ppfx+'[method]'].value) {
361 if (f[ppfx+'[method]'].value == tmp_meth) got_prod = true;
364 if (!got_prod) {
365 if (!confirm('" . xls('Warning: There is no product matching the contraceptive service.') . "'))
366 return false;
370 } // end match services to products
371 $s .= "
373 ++required_code_count;
376 if ($this->match_services_to_products) {
377 $s .= "
378 // The following applies to contraception for family planning clinics.
379 // Loop thru the products.
380 for (var lino = 0; f['{$prod}['+lino+'][drug_id]']; ++lino) {
381 var pfx = '{$prod}[' + lino + ']';
382 if (f[pfx + '[del]'] && f[pfx + '[del]'].checked) continue;
383 if (f[pfx + '[method]'] && f[pfx + '[method]'].value) {
384 var tmp_meth = f[pfx + '[method]'].value;
385 // Contraceptive products should normally include a corresponding method.
386 var got_svc = false;
387 for (var slino = 0; f['{$bill}[' + slino + '][code_type]']; ++slino) {
388 var spfx = '{$bill}[' + slino + ']';
389 if (f[spfx + '[del]'] && f[spfx + '[del]'].checked) continue;
390 if (f[spfx + '[method]'] && f[spfx + '[method]'].value) {
391 if (f[spfx + '[method]'].value == tmp_meth) got_svc = true;
394 if (!got_svc) {
395 if (!confirm('" . xls('Warning: There is no service matching the contraceptive product.') . "'))
396 return false;
399 ++required_code_count;
402 } // end match services to products
403 if (isset($GLOBALS['code_types']['MA'])) {
404 $s .= "
405 if (required_code_count == 0) {
406 if (!confirm('" . xls('You have not entered any clinical services or products. Click Cancel to add them. Or click OK if you want to save as-is.') . "')) {
407 return false;
413 $s .= "
414 // End contraception validation.
415 if (f.ippfconmeth) {
416 // Save the primary contraceptive method to its hidden form field.
417 f.ippfconmeth.value = max_contra_code;
419 return true;
422 return $s;