Odyssey
data.i
1 <?php
2 // =================================
3 // PAGE 1: Monthly billing page
4 // =================================
5 
6 function MonthlyBillingLoad($dbh, $isSimpleReport, $simpleReportMonth) {
7  try {
8  $warnings = array();
9  $running = array();
10 
11  $monthStipulation = $isSimpleReport ? "where m.billing_date = '" . prep_save($simpleReportMonth) . "'"
12  : "where m.billing_date > date_trunc('month', current_date) - interval '13 months'";
13  $sql = "select pi.id \"preinvoiceId\", m.billing_date \"month\", m.started_date \"runDate\", m.last_step_date \"lastStep\",
14  m.processing_flag \"step\", m.last_error_code \"status\", m.last_error_msg \"message\", pi.feature_detail_id \"featureBillingDetailId\",
15  pi.description \"invoiceDescription\", pi.billing_system_id \"billingSystemItem\", pi.quantity \"quantity\", pi.amount \"amount\",
16  trim(pi.cu_id) \"cu\", i.system_options \"options\", trim(i.name) \"cuName\",
17  trim(coalesce(si.display_name, pi.billing_system_id)) \"featureDescription\",
18  pi.sales_item_id \"salesItemId\", trim(coalesce(p.home_cu_desc, si.prod_id)) \"productDescription\"
19  from cubillmonth m
20  left join cubillpreinvoice pi on to_char(m.billing_date, 'YYYY-MM') = to_char(pi.billing_date, 'YYYY-MM')
21  left join cuinfo i on lower(pi.cu_id) = lower(i.user_name)
22  left join cubillsalesitem si on pi.sales_item_id = si.id
23  left join cuprodlist p on trim(si.prod_id) = trim(p.home_cu_code)
24  $monthStipulation order by m.billing_date desc, pi.cu_id, pi.feature_detail_id, pi.id";
25  $sth = db_query($sql, $dbh);
26  if (!$sth) {
27  throw new exception ("Select query failed.", 1);
28  }
29  $queryResults = db_fetch_all($sth);
30  $queryResults = $queryResults === false ? array() : $queryResults;
31 
32  $warnings = array();
33  if (!$isSimpleReport) {
34  // See if there are any pending processes and if so, make sure that process has not exited out due to error.
35  $sql = "select t.billing_date \"month\"
36  from (select distinct pi.billing_date from cubillpreinvoice pi where pi.processing_flag > 0 and pi.processing_flag < 99) t
37  inner join cubillmonth m on to_char(t.billing_date, 'YYYY-MM') = to_char(m.billing_date, 'YYYY-MM')
38  and (m.last_error_msg is null or trim (from m.last_error_msg) = '')
39  order by t.billing_date desc";
40  $sth = db_query($sql, $dbh);
41  if (!$sth) {
42  throw new exception ("Select query failed.", 2);
43  }
44 
45  $results = db_fetch_all($sth);
46  $running = $results === false ? array() : $results;
47 
48  $results = CheckForOverlapGaps($dbh, 0);
49  if ($results["status"] !== "000") {
50  throw new exception ($results["error"], 3);
51  }
52  $warnings = array_merge($warnings, $results["warnings"]);
53  }
54 
55  $records = array();
56  $cuTempArray = array();
57  $invoiceTempArray = array();
58  $totalAmountArray = array();
59  $currentMonth = "";
60  $currentCU = "";
61  $monthChanged = false;
62  $cuChanged = false;
63  $noCU = false;
64  $monthsInGrid = array();
65 
66  $countLabelsVariance = GetCountLabelVariance();
67  $countLabels = GetCountLabels();
68  $timetrackBillingItems = GetTimetrackBillingItems();
69  foreach($queryResults as $record) {
70  $monthChanged = false;
71  $cuChanged = false;
72  if ($currentMonth == "" || $currentMonth != $record["month"]) {
73  $currentMonth = $record["month"];
74  $monthsInGrid[] = $currentMonth;
75  $monthChanged = true;
76  }
77 
78  $noCU = $record["cu"] == "";
79  if ($currentCU == "" || $currentCU != $record["cu"]) {
80  $currentCU = $record["cu"];
81  $cuChanged = true;
82  }
83 
84  if ($monthChanged) {
85  $step = intval($record["step"]);
86 
87  if ($step == 0) {
88  $mainStatus = "Not Started";
89  } else if ($step < 100) {
90  $mainStatus = "Run Billing Started";
91  } else if ($step == 100) {
92  $mainStatus = "Run Billing Finished";
93  } else if ($step < 200) {
94  $mainStatus = "QBWC Started";
95  } else if ($step == 200) {
96  $mainStatus = "QBWC Finished";
97  } else {
98  $mainStatus = "???";
99  }
100 
101  if ($step == 200) {
102  $completeDate = $record["lastStep"];
103  } else {
104  $completeDate = "";
105  }
106  $records[] = array("month" => $currentMonth, "runDate" => $record["runDate"], "completeDate" => $completeDate, "mainStatus" => $mainStatus,
107  "status" => $record["status"], "message" => $record["message"]);
108  $cuTempArray[$currentMonth] = array();
109  $invoiceTempArray[$currentMonth] = array();
110  $totalAmountArray[$currentMonth] = array();
111  }
112 
113  if (!$noCU) { // No need to compile an empty detail grid underneath month
114  if ($monthChanged || $cuChanged) {
115  $cuTempArray[$currentMonth][] = GetCuOptions($currentCU, $currentMonth, $record["options"], $record["cuName"]);
116  $invoiceTempArray[$currentMonth][$currentCU] = array();
117  $totalAmountArray[$currentMonth][$currentCU] = 0.0;
118  }
119 
120  $totalAmountArray[$currentMonth][$currentCU] += floatval($record["amount"]);
121 
122  $billingId = $record["billingSystemItem"];
123  $salesItemId = $record["salesItemId"];
124  $featureDescription = $record["featureDescription"];
125  $timetrackBillingItem = HCU_array_key_value(strtolower($billingId), $timetrackBillingItems);
126  if (isset($countLabelsVariance[$billingId][$salesItemId])) {
127  $quantityLabel = $countLabelsVariance[$billingId][$salesItemId];
128  } else if (isset($countLabels[$billingId])) {
129  $quantityLabel = $countLabels[$billingId];
130  } else if ($timetrackBillingItem !== false) {
131  $quantityLabel = "Hours ($billingId)";
132  $featureDescription = $timetrackBillingItem;
133  } else {
134  $quantityLabel = "";
135  }
136  $invoiceTempArray[$currentMonth][$currentCU][] = array("featureBillingDetailId" => intval($record["featureBillingDetailId"]),
137  "salesItemId" => $record["salesItemId"],
138  "invoiceDescription" => $record["invoiceDescription"],
139  "quantity" => floatval($record["quantity"]), "quantityLabel" => $quantityLabel,
140  "amount" => floatval($record["amount"]), "preInvoiceId" => $record["preinvoiceId"],
141  "featureDescription" => $featureDescription,
142  "productDescription" => $record["productDescription"]);
143  }
144  }
145 
146  foreach($cuTempArray as $month => $array) {
147  for ($i = 0, $count = count($array); $i != $count; $i++) {
148  $cu = $array[$i]["cu"];
149  $array[$i]["invoices"] = $invoiceTempArray[$month][$cu];
150  $array[$i]["totalAmount"] = $totalAmountArray[$month][$cu];
151  }
152  $cuTempArray[$month] = $array;
153  }
154 
155  for($i = 0, $count = count($records); $i != $count; $i++) {
156  $thisMonth = $records[$i]["month"];
157  $records[$i]["cus"] = $cuTempArray[$thisMonth];
158  }
159 
160  $returnArray = array("status" => "000", "error" => array(), "record" => $records, "operation" => "read", "running" => $running, "warnings" => $warnings);
161  } catch (exception $e) {
162  $returnArray = array("status" => $e->getCode(), "error" => array($e->getMessage()));
163  }
164  return $returnArray;
165 }
166 
167 function CheckForOverlapGaps($dbh, $contractId) {
168  try {
169  $warnings = array();
170  $needsOverlapCheck = GetNeedsOverlapCheck();
171  $where = array("cd.prod_id in ('" . implode("', '", $needsOverlapCheck["check"]) . "')", "si.id not in (" . implode(", ", $needsOverlapCheck["not"]) . ")");
172  if ($contractId != 0) {
173  $where[] = "cd.sales_order_id = " . intval($contractId);
174  $where[] = "fd.billing_status = 1 or (fd.billing_status = 2 and to_char(fd.start_date, 'YYYY-MM') <= to_char(now(), 'YYYY-MM')
175  and (fd.end_date is null or to_char(fd.end_date, 'YYYY-MM') >= to_char(now(), 'YYYY-MM')))";
176  } else {
177  // Called from monthly billing page. Here what is missing in the pending records isn't relevant.
178  $where[] = "fd.billing_status = 2 and to_char(fd.start_date, 'YYYY-MM') <= to_char(now(), 'YYYY-MM')
179  and (end_date is null or to_char(fd.end_date, 'YYYY-MM') >= to_char(now(), 'YYYY-MM'))";
180  }
181 
182  $sql = "select trim(cd.prod_id) \"prodId\", fd.billing_template \"template\", trim(si.display_name) \"displayName\", fd.id \"id\", trim(c.cu_id) \"cu\",
183  fd.billing_status \"status\", fd.sales_item_id \"salesId\", trim(si.billing_system_id) \"billingId\",
184  trim(coalesce(p.home_cu_desc, si.prod_id)) \"prodName\", trim(coalesce(i.name, c.cu_id)) \"cuName\"
185  from cubillsalesorderdetail cd
186  inner join cubillfeaturedetail fd on cd.id = fd.sales_order_detail_id
187  inner join cubillsalesorder c on cd.sales_order_id = c.id
188  inner join cubillsalesitem si on fd.sales_item_id = si.id
189  left join cuprodlist p on si.prod_id = p.home_cu_code
190  left join cuinfo i on lower(c.cu_id) = lower(i.user_name) ";
191  if (count($where) != 0) {
192  $sql .= "where (" . implode(") and (", $where) . ") ";
193  }
194  $sql .= "order by c.cu_id, cd.prod_id, fd.billing_status";
195 
196  $sth = db_query($sql, $dbh);
197  if (!$sth) {
198  throw new exception ("Select query failed.", 1);
199  }
200 
201  $queryResults = db_fetch_all($sth);
202  $queryResults = $queryResults === false ? array() : $queryResults;
203 
204 
205  $currentProd = "";
206  $currentProdName = "";
207  $prods = array();
208  $cus = array();
209  $currentCU = "";
210  $currentCUName = "";
211  $masterIntervals = array();
212  $intervals = array();
213  $currentStatus = -1;
214 
215  $setupRecurring = GetSetupRecurring();
216 
217  foreach($queryResults as $record) {
218  if (isset($setupRecurring[$record["billingId"]]) && $setupRecurring[$record["billingId"]] != "recurring") {
219  continue; // Calculation is only valid for recurring product features.
220  }
221 
222  if ($contractId == 0 && ($currentCU == "" || $currentCU != $record["cu"])) {
223  $currentCU = $record["cu"];
224  $currentCUName = $record["cuName"];
225  $cus[$currentCU] = $currentCUName;
226  $masterIntervals[$currentCU] = array();
227  $intervals = $masterIntervals[$currentCU];
228  }
229 
230  if ($currentProd == "" || $currentProd != $record["prodId"]) {
231  $currentProd = $record["prodId"];
232  $currentProdName = $record["prodName"];
233  $prods[$currentProd] = $currentProdName;
234  $currentStatus = $record["status"];
235  $intervals[$currentProd] = array();
236  $intervals[$currentProd][$currentStatus] = array();
237  } else if ($currentStatus == -1 || $currentStatus != $record["status"]) {
238  $intervals[$currentProd][$currentStatus] = array();
239  }
240 
241  $decodedTemplate = HCU_JsonDecode($record["template"]);
242  $aStart = intval($decodedTemplate["qty1"]);
243  $aEnd = intval($decodedTemplate["qty2"]);
244 
245  $overlapsI = -1;
246  $extendsIs = array();
247  $theseIntervals = HCU_array_key_value($currentProd, $intervals);
248  $theseIntervals = $theseIntervals === false ? array() : HCU_array_key_value($currentStatus, $theseIntervals);
249  $theseIntervals = $theseIntervals === false ? array() : $theseIntervals;
250  for($i = 0, $count = count($theseIntervals); $i != $count; $i++) {
251  $bStart = intval($theseIntervals[$i]["start"]);
252  $bEnd = intval($theseIntervals[$i]["end"]);
253 
254  $overlaps = false;
255  $thisExtends = false;
256 
257  if ($aEnd == -1) {
258  if ($bEnd == -1) {
259  $overlaps = true;
260  } else {
261  $overlaps = $bEnd > $aStart;
262  $thisExtends = $aStart == $bEnd || $aStart == $bEnd + 1;
263  }
264  } else {
265  if ($bEnd == -1) {
266  $overlaps = $bStart < $aEnd;
267  $thisExtends = $aEnd == $bStart || $aEnd + 1 == $bStart;
268  } else {
269  $overlaps = ($bStart > $aStart && $bStart < $aEnd) || ($bEnd > $aStart && $bEnd < $aEnd);
270  $thisExtends = $aStart == $bEnd || $aStart == $bEnd + 1 || $aEnd == $bStart || $aEnd + 1 == $bStart;
271  }
272  }
273  if ($overlaps) {
274  $overlapsI = $i;
275  break;
276  } else if ($thisExtends) {
277  $extendsIs[] = $i;
278  }
279  }
280 
281  if ($overlapsI != -1) {
282  $pendingActive = $currentStatus == 1 ? "pending" : "active";
283 
284  if ($contractId == 0) {
285  if (!isset($warnings[$currentCU])) {
286  $warnings[$currentCU] = array();
287  }
288  if (!isset($warnings[$currentCU][$currentProd])) {
289  $warnings[$currentCU][$currentProd] = array();
290  }
291  $warnings[$currentCU][$currentProd][] = "$currentCUName ($currentCU): " . $record["displayName"] . " overlaps with another
292  $pendingActive $currentProdName record.";
293  } else {
294  if (!isset($warnings[$currentProd])) {
295  $warnings[$currentProd] = array();
296  }
297  $warnings[$currentProd][] = $record["displayName"] . " overlaps with another $pendingActive $currentProdName record.";
298  }
299 
300  } else if (count($extendsIs) != 0) {
301  $start = $aStart;
302  $end = $aEnd;
303  for($i = count($extendsIs) - 1; $i != -1; $i--) {
304  $index = $extendsIs[$i];
305  $bStart = $theseIntervals[$index]["start"];
306  $bEnd = $theseIntervals[$index]["end"];
307  if ($bStart < $aStart) {
308  $start = intval($bStart);
309  }
310  if ($end == -1 || $bEnd == -1) {
311  $end = -1;
312  } else if ($bEnd > $end) {
313  $end = intval($bEnd);
314  }
315  array_splice($theseIntervals, $index, 1);
316  }
317  $theseIntervals[] = array("start" => $start, "end" => $end);
318  } else {
319  $theseIntervals[] = array("start" => $aStart, "end" => $aEnd);
320  }
321 
322  // Setting a variable to a subset of an array in PHP doesn't change the original array. Thus this is needed.
323  $intervals[$currentProd][$currentStatus] = $theseIntervals;
324  if ($contractId == 0) {
325  $masterIntervals[$currentCU] = $intervals;
326  }
327  }
328 
329  if ($currentCU != "") { // In the case, that there are no records found.
330  if ($contractId != 0) {
331  $results = CheckForOverlapGapsEnd($intervals, $contractId, $currentCU, $warnings, $prods, $cus);
332  if ($results["status"] !== "000") {
333  throw new exception ($results["error"], 2);
334  }
335  $warnings = $results["warnings"];
336  } else {
337  foreach($masterIntervals as $currentCU => $intervals) {
338  $results = CheckForOverlapGapsEnd($intervals, $contractId, $currentCU, $warnings, $prods, $cus);
339  if ($results["status"] !== "000") {
340  throw new exception ($results["error"], 2);
341  }
342  $warnings = $results["warnings"];
343  }
344  }
345  }
346 
347 
348  $returnArray = array("status" => "000", "error" => "", "warnings" => $warnings);
349  } catch (exception $e) {
350  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "warnings" => $warnings);
351  }
352  return $returnArray;
353 }
354 
355 function CheckForOverlapGapsEnd($intervals, $contractId, $cu, $warnings, $prods, $cus) {
356  try {
357  $cuName = HCU_array_key_value($cu, $cus);
358  foreach($intervals as $prodKey => $records) {
359  $prodName = HCU_array_key_value($prodKey, $prods);
360  foreach($records as $currentStatus => $theseRecords) {
361  $pendingActive = $currentStatus == 1 ? "pending" : "active";
362  if (!(count($theseRecords) == 1 && $theseRecords[0]["start"] == 0 && $theseRecords[0]["end"] == -1)) {
363  // If the calculation is from the monthly billing page, then we also need the name of the CU.
364  if ($contractId == 0) {
365  if (!HCU_array_key_exists($cu, $warnings)) {
366  $warnings[$cu] = array();
367  }
368  if (!HCU_array_key_exists($prodKey, $warnings[$cu])) {
369  $warnings[$cu][$prodKey] = array("$cuName ($cu): $prodName is not contiguous from 0 to &infin; for $pendingActive records.");
370  }
371  } else {
372  if (!HCU_array_key_exists($prodKey, $warnings)) {
373  $warnings[$prodKey] = array("$prodName is not contiguous from 0 to &infin; for $pendingActive records.");
374  }
375  }
376  }
377  }
378  }
379 
380  $returnArray = array("status" => "000", "error" => "", "warnings" => $warnings);
381  } catch (exception $e) {
382  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "warnings" => $warnings);
383  }
384  return $returnArray;
385 }
386 
387 function GetCuOptions($currentCU, $currentMonth, $options, $cuName) {
388  // Borrowed from cu_top
389  $SYS_TYPE_BATCH = 1;
390  $SYS_TYPE_LIVE = 2;
391  $SYS_TYPE_WEBONLY = 16;
392 
393  $isBatch = ($options & $SYS_TYPE_BATCH) != 0;
394  $isLive = ($options & $SYS_TYPE_LIVE) != 0;
395  $isWebOnly = ($options & $SYS_TYPE_WEBONLY) != 0;
396  $type = $isWebOnly ? "Web Only" : ($isLive ? "Live" : "Batch");
397 
398  return array("cu" => $currentCU, "month" => $currentMonth, "type" => $type, "cuName" => $cuName);
399 }
400 
401 function MonthReport($dbh, $sysenv, $asCSV) {
402  try {
403  extract($sysenv["BILL"]);
404  $monthFormat = "Y-m-d|";
405  $month = $reportDate;
406  if (!isset($month) || trim($month) == "") {
407  throw new exception ("Month is required.", 1);
408  }
409  if (!DateTime::createFromFormat($monthFormat, $month)) {
410  throw new exception ("Month is invalid.", 2);
411  }
412 
413  $returnArray = MonthlyBillingLoad($dbh, true, $month);
414  if ($returnArray["status"] !== "000") {
415  throw new exception ($returnArray["error"], 3);
416  }
417 
418  if ($asCSV) {
419  $csvData = array();
420  $monthRecord = $returnArray["record"][0];
421 
422  $dateTime = new DateTime($monthRecord["month"]);
423  $month = $dateTime->format("F Y");
424  $monthFile = $dateTime->format("M_Y");
425 
426  $sql = "select id, display_name from cubillsalesitem order by display_name";
427  $sth = db_query($sql, $dbh);
428  if (!$sth) {
429  throw new exception("display query failed.", 3);
430  }
431  $displayMap = array();
432  for($i = 0; $row = db_fetch_assoc($sth, $i); $i++) {
433  $displayMap[intval($row["id"])] = trim($row["display_name"]);
434  }
435 
436  $csvData[] = "CU,Name," . implode(",", $displayMap) . ",Total";
437 
438  if (isset($monthRecord["cus"])) {
439  foreach($monthRecord["cus"] as $cuRecord) {
440  $cuRow = array($cuRecord["cu"], $cuRecord["cuName"]);
441  $totalFormatted = $cuRecord["totalAmount"] < 0 ? "\$(" . number_format(abs($cuRecord["totalAmount"]), 2) . ")"
442  : "\$" . number_format($cuRecord["totalAmount"], 2);
443  $cuFeatureMap = array();
444 
445  foreach($cuRecord["invoices"] as $invoiceRecord) {
446  $key = intval($invoiceRecord["salesItemId"]);
447  if (!isset($cuFeatureMap[$key])) {
448  $cuFeatureMap[$key] = 0;
449  }
450  $cuFeatureMap[$key] += $invoiceRecord["amount"];
451  }
452 
453  foreach($cuFeatureMap as $key => $value) {
454  $cuFeatureMap[$key] = $value < 0 ? "\"\$(" . number_format(abs($value), 2) . ")\"" : "\"\$" . number_format($value, 2) . "\"";
455  }
456 
457  foreach($displayMap as $id => $text) {
458  $cuRow[] = isset($cuFeatureMap[$id]) ? $cuFeatureMap[$id] : "";
459  }
460  $cuRow[] = "\"$totalFormatted\"";
461  $csvData[] = implode(",", $cuRow);
462  }
463  }
464  $csvData = implode("\r\n", $csvData);
465 
466  header("Content-length: " . strlen($csvData) );
467  header("Content-type: application/octetstream");
468  header("Content-disposition: inline; filename=\"monthlyReport_$monthFile.csv\"");
469  print ($csvData);
470 
471  exit;
472  }
473  } catch (exception $e) {
474  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
475  }
476  return $returnArray;
477 }
478 
479 function GetMonthlyProgressBars($dbh, $sysenv) {
480  try {
481  extract($sysenv["BILL"]);
482 
483  if ($reportDate != "") {
484  $monthSQLPart = "and pi.billing_date = '" . prep_save($reportDate) . "' ";
485  } else {
486  $monthSQLPart = "";
487  }
488 
489  // Gets a division of preinvoice by month and processed number if there is at least one record that is in a running state for QWBC.
490  // The time from state 25 to 99 is miminal.
491  $sql = "select pi.billing_date \"month\", pi.processing_flag \"flag\", count(*) \"count\"
492  from cubillpreinvoice pi
493  where pi.processing_flag >= 0 and pi.processing_flag <= 25 and exists (
494  select 'FOUND' from cubillpreinvoice i where i.processing_flag > 0 and i.processing_flag < 25
495  and pi.billing_date = i.billing_date) $monthSQLPart
496  group by pi.billing_date, pi.processing_flag order by pi.billing_date desc, pi.processing_flag";
497 
498  $sth = db_query($sql, $dbh);
499  if (!$sth) {
500  throw new exception ("Select query failed.", 2);
501  }
502  $queryResults = db_fetch_all($sth);
503  $queryResults = $queryResults === false ? array() : $queryResults;
504 
505  $averages = array();
506  $resultsArray = array();
507 
508  foreach($queryResults as $record) {
509  $month = $record["month"];
510  $count = intval($record["count"]);
511  $weight = intval($record["flag"]);
512  if (!HCU_array_key_exists($month, $averages)) {
513  $averages[$month] = array("num" => 0, "denom" => 0);
514  }
515  $averages[$month]["num"] += $count * $weight;
516  $averages[$month]["denom"] += $count;
517  }
518  foreach($averages as $month => $record) {
519  $weightedAverage = ($record["num"] / $record["denom"]);
520  $weightedAverage = $weightedAverage / 25.0;
521  $resultsArray[] = array("month" => $month, "progress" => $weightedAverage);
522  }
523 
524  $returnArray = array("status" => "000", "error" => "", "record" => $resultsArray);
525  } catch (exception $e) {
526  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
527  }
528  return $returnArray;
529 }
530 
531 // =================================
532 // END PAGE 1: Monthly billing page
533 // =================================
534 
535 // =================================
536 // PAGE 2: Sales Order
537 // =================================
538 
539 function SalesOrderLoad($dbh, $sysenv, $Hu) {
540  try {
541  extract($sysenv["BILL"]);
542  global $SYS_TYPE_CLOSED;
543  $billingEmailFlag = GetBillingEmailFlag();
544  $initializeDDLs = isset($initializeDDLs) ? $initializeDDLs == "Y" : false;
545 
546  $sql = "select so.id \"salesOrderId\", so.cu_id \"cuNumber\", so.date \"date\", so.reference \"refNo\", so.staff_id \"employee\",
547  so.flags & $billingEmailFlag \"emailable\", i.name \"cuName\", i.system_options & $SYS_TYPE_CLOSED \"isClosed\"
548  from cubillsalesorder so inner join cuinfo i on upper(so.cu_id) = upper(i.user_name) order by so.cu_id";
549  $sth = db_query($sql, $dbh);
550  if (!$sth) {
551  throw new exception ("Select query failed.", 1);
552  }
553  $queryResults = db_fetch_all($sth);
554  $queryResults = $queryResults === false ? array() : $queryResults;
555 
556  foreach($queryResults as $i => $record) {
557  $record["emailable"] = $record["emailable"] != 0;
558  $record["isClosed"] = $record["isClosed"] != 0;
559  $queryResults[$i] = $record;
560  }
561 
562  $cuList = array();
563  if ($initializeDDLs) {
564  $sql = "select i.user_name \"value\", i.name \"text\", i.system_options & $SYS_TYPE_CLOSED \"isClosed\" from cuinfo i order by i.user_name";
565  $sth = db_query($sql, $dbh);
566  if (!$sth) {
567  throw new exception ("Select query failed.", 2);
568  }
569  $cuList = db_fetch_all($sth);
570  $cuList = $cuList === false ? array() : $cuList;
571 
572  foreach($cuList as $i => $record) {
573  $record["isClosed"] = $record["isClosed"] != 0;
574  $cuList[$i] = $record;
575  }
576  }
577 
578  $returnArray = array("status" => "000", "error" => "", "record" => $queryResults, "cuList" => $cuList, "operation" => "read", "currentEmployeeId" => $Hu);
579  } catch (exception $e) {
580  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "operation" => "read");
581  }
582  return $returnArray;
583 }
584 
585 function SalesOrderCreateOrUpdate($dbh, $Hu, $isCreate, $sysenv) {
586  try {
587  extract($sysenv["BILL"]);
588  $billingEmailFlag = GetBillingEmailFlag();
589  if (!isset($salesOrderId) || trim($salesOrderId) == "") {
590  throw new exception ("sales Order Id is required.", 1);
591  }
592  $salesOrderId = intval($salesOrderId);
593  $cuNumber = isset($cuNumber) ? trim(strtoupper($cuNumber)) : "";
594  if (!isset($cuNumber) || trim($cuNumber) == "") {
595  throw new exception ("Cu number is required.", 2);
596  }
597  $reference = isset($refNo) ? trim($refNo) : "";
598  $emailable = isset($emailable) ? trim($emailable) == "Y" : false;
599  $emailableSQL = $emailable ? ($isCreate ? $billingEmailFlag : "flags | $billingEmailFlag") : ($isCreate ? 0 : "flags & ~$billingEmailFlag");
600 
601  $sql = "select name from cuinfo where lower(user_name) = '" . prep_save(strtolower($cuNumber), 12) . "'";
602  $sth = db_query($sql, $dbh);
603  if (!$sth) {
604  throw new exception ("Select query failed.", 3);
605  }
606  if (db_num_rows($sth) <= 0) {
607  throw new exception ("No Credit Union exists.", 4);
608  }
609  $cuName = trim(db_fetch_row($sth, 0)[0]);
610 
611  if ($isCreate) {
612  $dateTime = new DateTime();
613  $date = $dateTime->format("Y-m-d");
614  $sql = "insert into cubillsalesorder (cu_id, date, staff_id, reference, flags)
615  values ('" . prep_save($cuNumber, 10) . "', '" . prep_save($date) . "', '" . prep_save($Hu, 10) . "', '" . prep_save($reference, 20) . "',
616  $emailableSQL)
617  returning id \"salesOrderId\", cu_id \"cuNumber\", date \"date\", reference \"refNo\",
618  staff_id \"employee\", flags & $billingEmailFlag \"emailable\", '$cuName' \"cuName\"";
619 
620 
621  } else {
622  $sql = "update cubillsalesorder set cu_id = '" . prep_save($cuNumber, 10) . "', reference = '" . prep_save($reference, 20) . "',
623  flags = $emailableSQL where id = $salesOrderId
624  returning id \"salesOrderId\", cu_id \"cuNumber\", date \"date\", reference \"refNo\",
625  staff_id \"employee\", flags & $billingEmailFlag \"emailable\", '$cuName' \"cuName\"";
626  }
627 
628  $sth = db_query($sql, $dbh);
629  if (!$sth) {
630  throw new exception ("Upsert query failed.", 5);
631  }
632  $queryResults = db_fetch_assoc($sth, 0);
633  $queryResults["emailable"] = $queryResults["emailable"] != 0;
634 
635  $returnArray = array("status" => "000", "error" => "", "record" => $queryResults, "operation" => $isCreate ? "add" : "update");
636 
637  } catch (exception $e) {
638  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "operation" => $isCreate ? "add" : "update");
639  }
640  return $returnArray;
641 }
642 
643 function GetCuView($dbh, $sysenv) {
644  try {
645  extract($sysenv["BILL"]);
646  global $SYS_TYPE_BATCH, $SYS_TYPE_LIVE, $SYS_TYPE_WEBONLY, $SYS_TYPE_CLOSED;
647  $cu = isset($cu) ? strtoupper(trim($cu)) : "";
648  if ($cu == "") {
649  throw new exception ("Cu is required.", 1);
650  }
651 
652  $initializeDDLs = isset($initializeDDLs) ? trim($initializeDDLs) == "Y" : false;
653 
654  $sql = "select trim(ci.user_name) \"cu\", so.date \"contractEnteredDate\", trim(ci.name) \"cuName\", ci.system_options \"options\",
655  fd.start_date \"fdStartDate\", fd.end_date \"fdEndDate\", fd.billing_template \"template\", fd.sales_item_id \"salesItemId\",
656  trim(si.billing_system_id) \"billingSystemId\", trim(si.display_name) \"salesItemDescr\", fd.billing_status \"billingStatus\",
657  fd.bill_half_boolean \"billHalf\", fd.id \"featureDetailId\", sod.prod_id \"prodId\", so.id \"salesOrderId\",
658  sod.id \"orderDetailId\", fd.partially_billed_date \"partiallyBilledDate\",
659  exists (select 'FOUND' from cubillpreinvoice pi where fd.id = pi.feature_detail_id) \"linkExists\",
660  fd.partially_billed_amount \"partiallyBilledAmount\"
661  from (select i.user_name, i.system_options, i.name from cuinfo i where upper(i.user_name) = '" . prep_save($cu, 10) . "') ci
662  left join cubillsalesorder so on upper(ci.user_name) = upper(so.cu_id)
663  left join cubillsalesorderdetail sod on so.id = sod.sales_order_id
664  left join cubillfeaturedetail fd on sod.id = fd.sales_order_detail_id
665  left join cubillsalesitem si on fd.sales_item_id = si.id
666  order by si.display_name";
667  $sth = db_query($sql, $dbh);
668  if (!$sth) {
669  throw new exception ("Select query failed.", 1);
670  }
671  $queryResults = db_fetch_all($sth);
672  $queryResults = $queryResults === false ? array() : $queryResults;
673 
674  $first = true;
675  $topLine = array();
676  $itemLines = array();
677 
678  $setupRecurring = GetSetupRecurring();
679  foreach($queryResults as $record) {
680  // Create top portion
681  if ($first) {
682  // Extract type and is closed from system options
683  $options = $record["options"];
684 
685  $isBatch = ($options & $SYS_TYPE_BATCH) != 0;
686  $isLive = ($options & $SYS_TYPE_LIVE) != 0;
687  $isWebOnly = ($options & $SYS_TYPE_WEBONLY) != 0;
688  $isClosed = ($options & $SYS_TYPE_CLOSED) != 0;
689  $type = $isWebOnly ? "Web Only" : ($isLive ? "Live" : "Batch");
690  $closed = $isClosed ? "Yes" : "No";
691 
692  $topLine[] = array("cu" => $record["cu"], "cuName" => $record["cuName"], "type" => $type, "isClosed" => $isClosed,
693  "salesOrderId" => $record["salesOrderId"]);
694  $first = false;
695  }
696 
697  // Create line items
698  if ($record["featureDetailId"] != "") {
699  // Extract template and then get description.
700  $expandedTemplate = HCU_JsonDecode($record["template"]);
701  $salesItemId = intval($record["salesItemId"]);
702  $billingId = $record["billingSystemId"];
703  $fixed = floatval($expandedTemplate["fixed"]);
704  $variable = floatval($expandedTemplate["variable"]);
705  $qty1 = intval($expandedTemplate["qty1"]);
706  $qty2 = intval($expandedTemplate["qty2"]);
707  $aDNID = HCU_array_key_exists("aDNID", $expandedTemplate) ? $expandedTemplate["aDNID"] : "";
708  $originalDescription = $expandedTemplate["descr"] == null || $expandedTemplate["descr"] == "" ? $record["salesItemDescr"]
709  : $expandedTemplate["descr"];
710 
711  $description = GetTextDescription($salesItemId, $billingId, $fixed, $variable, $qty1, $qty2, $aDNID);
712  $setupLabel = $setupRecurring[$record["billingSystemId"]];
713  $setup = $setupLabel == "setup";
714  if (!isset($setup)) {
715  $setup = false;
716  }
717  $results = GetBillingStatusLabel(intval($record["billingStatus"]), $record["fdStartDate"], $record["fdEndDate"], $setupLabel);
718  if ($results["status"] !== "000") {
719  throw new exception ($results["error"], 2);
720  }
721  extract($results["data"]);
722  $billHalf = $record["billHalf"] != 0;
723  $billHalfLabel = $billHalf ? "Yes" : "No";
724  $initialRecurringCosts = GetInitialRecurringCosts($salesItemId, $billingId, $fixed, $variable, $qty1, $qty2);
725 
726  $results = GetHalfBillingStatus($setup, $billHalf, $record["partiallyBilledDate"], $record["fdStartDate"]);
727  if ($results["status"] !== "000") {
728  throw new exception ($results["error"], 3);
729  }
730  extract($results["data"]);
731 
732  if ($record["linkExists"] == "t") {
733  $deletable = "N";
734  } else {
735  $dateTime = new DateTime ($record["fdStartDate"]);
736  $firstOfMonth = new DateTime();
737  $firstOfMonth->modify("first day of this month");
738  $interval = $dateTime->diff($firstOfMonth);
739  $dateDiff = $interval->format('%R%a');
740  if ($dateDiff <= 0) {
741  $deletable = "Y";
742  } else {
743  $deletable = "N";
744  }
745  }
746 
747  $itemLines[] = array("featureDetailId" => $record["featureDetailId"], "textDescription" => $description, "statusLabel" => $statusLabel,
748  "billHalf" => $billHalf, "initialCosts" => $initialRecurringCosts[0], "recurringCosts" => $initialRecurringCosts[1],
749  "qty1" => $qty1, "qty2" => $qty2, "salesItemId" => intval($record["salesItemId"]), "linkExists" => $record["linkExists"],
750  "salesItemDescr" => $record["salesItemDescr"], "billingStatus" => intval($record["billingStatus"]), "fixed" => $fixed,
751  "variable" => $variable, "prodId" => $record["prodId"], "setup" => $setup, "originalDescription" => $originalDescription,
752  "actualDescription" => $record["salesItemDescr"], "description" => "", "deletable" => $deletable,
753  "orderDetailId" => intval($record["orderDetailId"]), "templateId" => $record["featureDetailId"],
754  "billingStatusGroup" => $billingStatusGroup, "partiallyBilledDate" => $record["partiallyBilledDate"],
755  "partiallyBilledAmount" => $record["partiallyBilledAmount"]);
756  }
757  }
758 
759  $returnArray = array("status" => "000", "error" => "", "top" => $topLine, "bottom" => $itemLines);
760  } catch (exception $e) {
761  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "top" => array(), "bottom" => array());
762  }
763  return $returnArray;
764 }
765 
766 function GetProductsWithSetups($dbh) {
767  try {
768  $setups = array();
769  $productsWithSetupOptions = array();
770  $setupRecurring = GetSetupRecurring();
771  foreach($setupRecurring as $billingItem => $setupLabel) {
772  if ($setupLabel == "setup")
773  $setups[] = $billingItem;
774  }
775 
776  if (count($setups) > 0) {
777  $sql = "select id \"id\", billing_template \"template\" from cubillsalesitem where billing_system_id in ('" . implode("', '", $setups) . "')";
778  $sth = db_query($sql, $dbh);
779  if (!$sth) {
780  throw new exception ("Select query failed.", 1);
781  }
782  $queryResults = db_fetch_all($sth);
783  $queryResults = $queryResults === false ? array() : $queryResults;
784 
785  $setupsWithOptions = array();
786 
787  foreach($queryResults as $record) {
788  $template = HCU_JsonDecode($record["template"]);
789  if (intval($template["fixed"]) != 0) {
790  $setupsWithOptions[$record["id"]] = true;
791  }
792  }
793 
794  $productsWithSetupOptions = array();
795  $productAdds = GetAutomaticProductAdds();
796  foreach($productAdds as $product => $configurations) {
797  $hasNonZeroSetups = false;
798  foreach($configurations as $configuration) {
799  if (HCU_array_key_exists($configuration["salesItemId"], $setupsWithOptions)) {
800  $hasNonZeroSetups = true;
801  break;
802  }
803  }
804 
805  $productsWithSetupOptions[$product] = $hasNonZeroSetups;
806  }
807  }
808 
809  $returnArray = array("status" => "000", "error" => "", "productsWithSetupOptions" => $productsWithSetupOptions);
810 
811  } catch (exception $e) {
812  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
813  }
814  return $returnArray;
815 }
816 
817 function MostExpensiveCmpFunc($a, $b, $divideByCus = false) {
818  if ($divideByCus) {
819  $aCu = strtolower($a["cu"]);
820  $bCu = strtolower($b["cu"]);
821  if ($aCu < $bCu) {
822  return -1;
823  } else if ($aCu > $bCu) {
824  return 1;
825  }
826  }
827 
828  $aFixed = $a["fixed"];
829  $aVariable = $a["variable"];
830  $bFixed = $b["fixed"];
831  $bVariable = $b["variable"];
832 
833  if ($aFixed > $bFixed) {
834  return -1;
835  } else if ($aFixed < $bFixed) {
836  return 1;
837  }
838 
839  if ($aVariable > $bVariable) {
840  return -1;
841  } else if ($aVariable < $bVariable) {
842  return 1;
843  } else {
844  return 0;
845  }
846 }
847 
848 function MostExpensiveCmpLayeredFunc($a, $b) {
849  $aFixed = HCU_array_key_value("fixed", $a["featureDetailList"][0]);
850  $aVariable = HCU_array_key_value("variable", $a["featureDetailList"][0]);
851  $bFixed = HCU_array_key_value("fixed", $b["featureDetailList"][0]);
852  $bVariable = HCU_array_key_value("variable", $b["featureDetailList"][0]);
853  if (!isset($aFixed)) {
854  $aFixed = -1;
855  }
856  if (!isset($aVariable)) {
857  $aVariable = -1;
858  }
859  if (!isset($bFixed)) {
860  $bFixed = -1;
861  }
862  if (!isset($bVariable)) {
863  $bVariable = -1;
864  }
865 
866  if ($aFixed > $bFixed) {
867  return -1;
868  } else if ($aFixed < $bFixed) {
869  return 1;
870  }
871 
872  if ($aVariable > $bVariable) {
873  return -1;
874  } else if ($aVariable < $bVariable) {
875  return 1;
876  } else {
877  return 0;
878  }
879 }
880 
881 function ByProductAndSetupCmpFunc($a, $b, $sortedProdArray, $dividedByBillingStatusGroup = false, $dividedByCus = false) {
882  if ($dividedByBillingStatusGroup) {
883  $aBillingStatusGroup = intval($a["billingStatusGroup"]);
884  $bBillingStatusGroup = intval($b["billingStatusGroup"]);
885  if ($aBillingStatusGroup < $bBillingStatusGroup) {
886  return -1;
887  } else if ($aBillingStatusGroup > $bBillingStatusGroup) {
888  return 1;
889  }
890 
891  $aProductRank = $sortedProdArray[$aBillingStatusGroup][$a["prodId"]];
892  $bProductRank = $sortedProdArray[$bBillingStatusGroup][$b["prodId"]];
893  } else if ($dividedByCus) {
894  $aCu = strtolower($a["cu"]);
895  $bCu = strtolower($b["cu"]);
896  if ($aCu < $bCu) {
897  return -1;
898  } else if ($aCu > $bCu) {
899  return 1;
900  }
901 
902  $aProductRank = $sortedProdArray[$aCu][$a["prodId"]];
903  $bProductRank = $sortedProdArray[$bCu][$b["prodId"]];
904  } else {
905  $aProductRank = $sortedProdArray[$a["prodId"]];
906  $bProductRank = $sortedProdArray[$b["prodId"]];
907  }
908 
909  if (!isset($aProductRank)) {
910  $aProductRank = -1;
911  }
912  if (!isset($bProductRank)) {
913  $bProductRank = -1;
914  }
915  if ($aProductRank > $bProductRank) {
916  return -1;
917  } else if ($aProductRank < $bProductRank) {
918  return 1;
919  }
920 
921  $aSetupRank = $a["setup"] ? 1 : 0;
922  $bSetupRank = $b["setup"] ? 1 : 0;
923  if ($aSetupRank > $bSetupRank) {
924  return -1;
925  } else if ($aSetupRank < $bSetupRank) {
926  return 1;
927  }
928 
929  return MostExpensiveCmpFunc($a, $b);
930 }
931 
932 
933 // =================================
934 // END PAGE 2: Sales Order
935 // =================================
936 
937 // =================================
938 // PAGE 3: Dashboard
939 // =================================
940 
941 function ReadDashboard($dbh) {
942  try {
943  $sql = "select ci.user_name \"cu\", ci.name \"cuName\", fd.start_date \"fdStartDate\", fd.end_date \"fdEndDate\", fd.billing_template \"template\",
944  fd.sales_item_id \"salesItemId\", si.billing_system_id \"billingSystemId\", si.display_name \"salesItemDescr\", sod.prod_id \"prodId\",
945  fd.billing_status \"billingStatus\", fd.bill_half_boolean \"billHalf\", fd.id \"templateId\", so.id \"orderId\"
946  from cubillsalesorder so
947  inner join cuinfo ci on upper(so.cu_id) = upper(ci.user_name)
948  inner join cubillsalesorderdetail sod on so.id = sod.sales_order_id
949  inner join cubillfeaturedetail fd on sod.id = fd.sales_order_detail_id
950  left join cubillsalesitem si on fd.sales_item_id = si.id where fd.billing_status in (0,1)";
951  $sth = db_query($sql, $dbh);
952  if (!$sth) {
953  throw new exception ("Select query failed.", 1);
954  }
955  $queryResults = db_fetch_all($sth);
956  $queryResults = $queryResults === false ? array() : $queryResults;
957 
958  $newRecords = array();
959  $setupRecurring = GetSetupRecurring();
960  foreach($queryResults as $record) {
961  $setupLabel = HCU_array_key_value($record["billingSystemId"], $setupRecurring);
962  if ($setupLabel == "triggered") {
963  continue; // Triggered records are doing their own thing
964  }
965  $billHalf = trim($record["billHalf"]) == "Y";
966  $salesItemId = intval($record["salesItemId"]);
967  $template = HCU_JsonDecode($record["template"]);
968  $fixed = floatval($template["fixed"]);
969  $variable = floatval($template["variable"]);
970  $qty1 = intval($template["qty1"]);
971  $qty2 = intval($template["qty2"]);
972  $aDNID = HCU_array_key_exists("aDNID", $template) ? $template["aDNID"] : "";
973  $billingId = $record["billingSystemId"];
974  $description = GetTextDescription($salesItemId, $billingId, $fixed, $variable, $qty1, $qty2, $aDNID);
975 
976  $setup = $setupLabel == "setup";
977  $results = GetBillingStatusLabel(intval($record["billingStatus"]), $record["fdStartDate"], $record["fdEndDate"], $setupLabel);
978  if ($results["status"] !== "000") {
979  throw new exception ($results["error"], 2);
980  }
981  extract($results["data"]);
982  $initialRecurringCosts = GetInitialRecurringCosts($salesItemId, $billingId, $fixed, $variable, $qty1, $qty2);
983 
984  $newRecords[] = array("templateId" => intval($record["templateId"]), "cu" => $record["cu"], "cuName" => $record["cuName"],
985  "textDescription" => $description, "salesItemDescr" => $record["salesItemDescr"], "statusLabel" => $statusLabel,
986  "billHalf" => $billHalf, "initialCosts" => $initialRecurringCosts[0], "recurringCosts" => $initialRecurringCosts[1],
987  "orderId" => intval($record["orderId"]), "prodId" => $record["prodId"], "fixed" => $fixed, "variable" => $variable,
988  "setup" => $setup); // For sorting
989  }
990 
991  $newRecords = SortDashboard($newRecords);
992 
993  $returnArray = array("status" => "000", "error" => "", "record" => $newRecords, "operation" => "read");
994  } catch (exception $e) {
995  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
996  }
997  return $returnArray;
998 }
999 
1000 function SortDashboard($records) {
1001  $sortedProdArray = array();
1002 
1003  usort($records, function($a, $b) {
1004  return MostExpensiveCmpFunc($a, $b, true);
1005  });
1006  $index = count($records) + 1;
1007  $indices = array();
1008  foreach($records as $record) {
1009  $prodId = $record["prodId"];
1010  $cu = strtolower($record["cu"]);
1011  if (!isset($sortedProdArray[$cu])) {
1012  $sortedProdArray[$cu] = array();
1013  $indices[$cu] = $index;
1014  }
1015  if (!isset($sortedProdArray[$cu][$prodId])) {
1016  $sortedProdArray[$cu][$prodId] = $indices[$cu]--;
1017  }
1018  }
1019  usort($records, function($a, $b) use ($sortedProdArray) {
1020  return ByProductAndSetupCmpFunc($a, $b, $sortedProdArray, false, true);
1021  });
1022 
1023  for($i = 0, $count = count($records); $i != $count; $i++) {
1024  $records[$i]["sortingId"] = $i + 1;
1025  }
1026 
1027  return $records;
1028 }
1029 
1030 function ReadWorkflowStatusInfo($dbh, $sysenv) {
1031  try {
1032  extract($sysenv["BILL"]);
1033 
1034  if (!isset($featureId)) {
1035  throw new exception ("Feature id is required.", 1);
1036  }
1037  $featureId = intval($featureId);
1038 
1039  $sql = "select t.track_id \"trackId\", t.issue \"issue\", t.status \"issueStatus\", t.assigned_to \"issueOwner\", t.entry_date \"entryDate\",
1040  t.last_activity_date \"lastActivityDate\", temp.title \"nextTask\", temp.assigned_to \"nextTaskOwner\"
1041  from cutrack t
1042  left join (
1043  select row_number() over (partition by ti.track_id, ti.parent, ti.task_status order by ti.view_order, ti.trackitem_id) as number,
1044  ti.title, ti.assigned_to, ti.track_id
1045  from cutrackitem ti
1046  where ti.task_status = 1 and ti.parent = 0
1047  ) temp on t.track_id = temp.track_id and temp.number = 1
1048  where t.billing_feature_id = " . intval($featureId);
1049  $sth = db_query($sql, $dbh);
1050  if (!$sth) {
1051  throw new exception ("Select query failed.", 1);
1052  }
1053  $queryResults = db_fetch_all($sth);
1054  $queryResults = $queryResults === false ? array() : $queryResults;
1055 
1056  $returnArray = array("status" => "000", "error" => "", "record" => $queryResults);
1057  } catch (exception $e) {
1058  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
1059  }
1060  return $returnArray;
1061 }
1062 
1063 // =================================
1064 // END PAGE 3: Dashboard
1065 // =================================
1066 
1067 // =================================
1068 // PAGE 4: Sales Items
1069 // =================================
1070 
1071 function SalesItemLoad($dbh) {
1072  try {
1073  $sql = "select trim(coalesce(p.home_cu_desc, si.prod_id)) \"prodDescr\", si.id \"productId\",
1074  trim(si.display_name) \"description\", trim(si.billing_system_id) \"billingId\",
1075  si.billing_template \"template\", trim(si.prod_id) \"prodName\", si.calc_id \"calcId\", si.frequency \"frequency\", si.months_to_bill \"billsOn\"
1076  from cubillsalesitem si
1077  left join cuprodlist p on si.prod_id = p.home_cu_code
1078  where si.deleted_date is null
1079  order by coalesce(p.home_cu_desc, si.prod_id), si.display_name";
1080  $sth = db_query($sql, $dbh);
1081  if (!$sth) {
1082  throw new exception ("Select query failed.", 1);
1083  }
1084  $salesItems = db_fetch_all($sth);
1085  $salesItems = $salesItems === false ? array() : $salesItems;
1086 
1087  $billingItems = GetBillingItems();
1088  for($i = 0, $count = count($salesItems); $i != $count; $i++) {
1089  $billingId = $salesItems[$i]["billingId"];
1090  $salesItems[$i]["billingIdDescr"] = HCU_array_key_value($billingId, $billingItems);
1091  }
1092 
1093  $sql = "select trim(p.home_cu_code) \"value\", trim(p.home_cu_desc) \"text\" from cuprodlist p order by p.home_cu_desc";
1094  $sth = db_query($sql, $dbh);
1095  if (!$sth) {
1096  throw new exception ("Select query failed.", 2);
1097  }
1098  $prodDDL = db_fetch_all($sth);
1099  $prodDDL = $prodDDL === false ? array() : $prodDDL;
1100 
1101  $setupConfig = GetSetupRecurring();
1102  $quantityLabelTypeVariance = GetQuantityLabelTypeVariance();
1103  $quantityLabelType = GetQuantityLabelType();
1104  $quantityLabelsVariance = GetQuantityLabelsVariance();
1105  $quantityLabels = GetQuantityLabels();
1106  $fixedVariableLabelTypeVariance = GetFixedVariableLabelTypeVariance();
1107  $fixedVariableLabelType = GetFixedVariableLabelType();
1108  $fixedVariableLabels = GetFixedVariableLabels();
1109  $fixedVariableLabelsVariance = GetFixedVariableLabelsVariance();
1110  $useDNID = GetUseDNID();
1111  $decimals = GetSaveDigits();
1112 
1113  $billingItemsDDL = array();
1114  foreach($billingItems as $billingId => $description) {
1115  $configuration = array();
1116  if (HCU_array_key_exists($billingId, $setupConfig)) {
1117  $configuration["setup"] = $setupConfig[$billingId] == "setup";
1118  }
1119  if (HCU_array_key_exists($billingId, $quantityLabelTypeVariance)) {
1120  $configuration["quantityLabelTypeVariance"] = $quantityLabelTypeVariance[$billingId];
1121  }
1122  if (HCU_array_key_exists($billingId, $quantityLabelType)) {
1123  $configuration["quantityLabelType"] = $quantityLabelType[$billingId];
1124  }
1125  if (HCU_array_key_exists($billingId, $quantityLabelsVariance)) {
1126  $configuration["quantityLabelsVariance"] = $quantityLabelsVariance[$billingId];
1127  }
1128  if (HCU_array_key_exists($billingId, $quantityLabels)) {
1129  $configuration["quantityLabels"] = $quantityLabels[$billingId];
1130  }
1131  if (HCU_array_key_exists($billingId, $fixedVariableLabelTypeVariance)) {
1132  $configuration["fixedVariableLabelTypeVariance"] = $fixedVariableLabelTypeVariance[$billingId];
1133  }
1134  if (HCU_array_key_exists($billingId, $fixedVariableLabelType)) {
1135  $configuration["fixedVariableLabelType"] = $fixedVariableLabelType[$billingId];
1136  }
1137  if (HCU_array_key_exists($billingId, $fixedVariableLabelsVariance)) {
1138  $configuration["fixedVariableLabelsVariance"] = $fixedVariableLabelsVariance[$billingId];
1139  }
1140  if (HCU_array_key_exists($billingId, $fixedVariableLabels)) {
1141  $configuration["fixedVariableLabels"] = $fixedVariableLabels[$billingId];
1142  }
1143 
1144  $configuration["useDNID"] = HCU_array_key_exists($billingId, $useDNID) ? $useDNID[$billingId] : false;
1145  $configuration["decimals"] = HCU_array_key_value($billingId, $decimals);
1146  $configuration["decimals"] = $configuration["decimals"] === false ? 2 : intval($configuration["decimals"]);
1147 
1148  $billingItemsDDL[] = array("text" => $description, "value" => $billingId, "configuration" => $configuration);
1149  }
1150 
1151  for($i = 0, $count = count($salesItems); $i != $count; $i++) {
1152  $billsOn = intval($salesItems[$i]["billsOn"]);
1153  $billingId = $salesItems[$i]["billingId"];
1154  $setup = HCU_array_key_value($billingId, $setupConfig);
1155 
1156  $results = GetFrequencyLabel($billsOn, $setup);
1157  extract($results["data"]);
1158  $salesItems[$i]["frequencyDescr"] = $frequencyLabel;
1159  $salesItems[$i]["billsOn"] = $billsOnKendo;
1160  }
1161 
1162  $returnArray = array("status" => "000", "error" => "", "billingItemDDL" => $billingItemsDDL, "record" => $salesItems,
1163  "productDDL" => $prodDDL, "operation" => "read");
1164  } catch (exception $e) {
1165  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "operation" => "read");
1166  }
1167  return $returnArray;
1168 }
1169 
1170 function SalesItemCreateOrUpdate($dbh, $isCreate, $sysenv) {
1171  try {
1172  extract($sysenv["BILL"]);
1173 
1174  if (!$isCreate) {
1175  if (!isset($productId) || trim($productId) == "") {
1176  throw new exception ("Product is required.", 1);
1177  }
1178  }
1179  $productId = isset($productId) ? intval($productId) : 0;
1180 
1181  if (!isset($prodName) || trim($prodName) == "") {
1182  throw new exception ("Name is required.", 2);
1183  }
1184  $name = trim($prodName);
1185 
1186  $description = isset($description) ? trim($description) : "";
1187  $billingId = isset($billingId) ? trim($billingId) : "";
1188  $fixed = isset($fixed) ? floatval($fixed) : 0.0;
1189  $variable = isset($variable) ? floatval($variable) : 0.0;
1190  $qty1 = isset($qty1) ? intval($qty1) : 0;
1191  $qty2 = isset($qty2) ? intval($qty2) : 0;
1192  $calcId = isset($calcId) ? intval($calcId) : 0;
1193  $billsOn = isset($billsOn) ? trim($billsOn) : "";
1194 
1195  $templateArray = array();
1196  $templateArray["descr"] = "";
1197  // Templates have the fixed and variable with two decimal places.
1198  $templateArray["fixed"] = $fixed;
1199  $templateArray["variable"] = $variable;
1200  $templateArray["qty1"] = $qty1;
1201  $templateArray["qty2"] = $qty2;
1202  $template = HCU_JsonEncode($templateArray);
1203 
1204  $results = GetFrequencyBillsOn($billsOn);
1205  if ($results["status"] !== "000") {
1206  throw new exception($results["error"], 3);
1207  }
1208  extract($results["data"]);
1209 
1210  $sql = "select home_cu_desc from cuprodlist where home_cu_code = '$name'";
1211  $sth = db_query($sql, $dbh);
1212  if (!$sth) {
1213  throw new exception ("Select query failed.", 4);
1214  }
1215  $prodListDesc = db_num_rows($sth) > 0 ? trim(db_fetch_row($sth, 0)[0]) : "";
1216 
1217  if ($isCreate) {
1218  $sql = "insert into cubillsalesitem (prod_id, display_name, billing_system_id, billing_template, calc_id, frequency, months_to_bill)
1219  values ('" . prep_save($name, 10) . "', '" . prep_save($description, 20) . "', '" . prep_save($billingId, 20) . "', '" . prep_save($template) . "',
1220  " . intval($calcId) . ", " . intval($frequency) . ", " . intval($billsOnInt) . ")
1221  returning coalesce('$prodListDesc', prod_id) \"prodDescr\", id \"productId\", display_name \"description\", billing_system_id \"billingId\",
1222  prod_id \"prodName\", billing_template \"template\", calc_id \"calcId\", frequency \"frequency\", months_to_bill \"billsOn\"";
1223  } else {
1224  $sql = "update cubillsalesitem set prod_id = '" . prep_save($name, 10) . "', billing_system_id = '" . prep_save($billingId, 20) . "',
1225  billing_template = '" . prep_save($template) . "', display_name = '" . prep_save($description, 20) . "',
1226  calc_id = " . intval($calcId) . ", frequency = " . intval($frequency) . ", months_to_bill = " . intval($billsOnInt) . "
1227  where id = " . intval($productId) . "
1228  returning coalesce('$prodListDesc', prod_id) \"prodDescr\", id \"productId\", display_name \"description\", billing_system_id \"billingId\",
1229  prod_id \"prodName\", billing_template \"template\", calc_id \"calcId\", frequency \"frequency\", months_to_bill \"billsOn\"";
1230  }
1231  $sth = db_query($sql, $dbh);
1232  if (!$sth) {
1233  throw new exception ("Upsert query failed.", 5);
1234  }
1235  $queryResults = db_fetch_all($sth);
1236  $queryResults = $queryResults === false ? array() : $queryResults;
1237 
1238  $billingItems = GetBillingItems();
1239  $setupRecurring = GetSetupRecurring();
1240  for($i = 0, $count = count($queryResults); $i != $count; $i++) {
1241  $billingId = $queryResults[$i]["billingId"];
1242  $setupLabel = HCU_array_key_value($billingId, $setupRecurring);
1243  $setupLabel = $setupLabel == "setup";
1244  $queryResults[$i]["billingIdDescr"] = HCU_array_key_value($billingId, $billingItems);
1245  $billsOn = intval($queryResults[$i]["billsOn"]);
1246 
1247  $results = GetFrequencyLabel($billsOn, $setupLabel);
1248  extract($results["data"]);
1249 
1250  $queryResults[$i]["frequencyDescr"] = $frequencyLabel;
1251  $queryResults[$i]["billsOn"] = $billsOnKendo;
1252  }
1253 
1254  $returnArray = array("status" => "000", "error" => "", "record" => $queryResults, "operation" => $isCreate ? "add" : "update");
1255 
1256  } catch (exception $e) {
1257  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "operation" => $isCreate ? "add" : "update");
1258  }
1259  return $returnArray;
1260 }
1261 
1262 function SalesItemRemove($dbh, $sysenv) {
1263  try {
1264  extract($sysenv["BILL"]);
1265  if (!isset($productId) || trim($productId) == "") {
1266  throw new exception ("Product is required.", 1);
1267  }
1268  $productId = intval($productId);
1269 
1270  $sql = "update cubillsalesitem set deleted_date = now() where id = " . intval($productId);
1271  $sth = db_query($sql, $dbh);
1272  if (!$sth) {
1273  throw new exception ("Update query failed.", 2);
1274  }
1275 
1276  $returnArray = array("status" => "000", "error" => "");
1277 
1278  } catch (exception $e) {
1279  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
1280  }
1281  return $returnArray;
1282 }
1283 
1284 function GetDeletedSalesItems($dbh) {
1285  try {
1286  $sql = "select coalesce(p.home_cu_desc, si.prod_id) \"productName\", si.id \"salesItemId\", si.display_name \"displayName\",
1287  si.billing_system_id \"billingId\", si.deleted_date \"deletedDate\"
1288  from cubillsalesitem si
1289  left join cuprodlist p on si.prod_id = p.home_cu_code
1290  where si.deleted_date is not null
1291  order by coalesce(p.home_cu_desc, si.prod_id), si.display_name";
1292  $sth = db_query($sql, $dbh);
1293  if (!$sth) {
1294  throw new exception ("Select query failed.", 1);
1295  }
1296  $queryResults = db_fetch_all($sth);
1297  $queryResults = $queryResults === false ? array() : $queryResults;
1298 
1299  $billingItems = GetBillingItems();
1300  for($i = 0, $count = count($queryResults); $i != $count; $i++) {
1301  $billingId = $queryResults[$i]["billingId"];
1302  $queryResults[$i]["billingName"] = HCU_array_key_value($billingId, $billingItems);
1303  }
1304 
1305  $returnArray = array("status" => "000", "error" => "", "record" => $queryResults);
1306  } catch (exception $e) {
1307  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
1308  }
1309  return $returnArray;
1310 }
1311 
1312 // =================================
1313 // END PAGE 4: Sales Items
1314 // =================================
1315 
1316 // =================================
1317 // PAGE 5: Sales Order Detail
1318 // =================================
1319 
1320 /**
1321  * function GetWhereForPendingLineItems()
1322  * @return String of the where clause for pending line items.
1323  */
1324 function GetWhereForPendingLineItems() {
1325  $setupRecurring = GetSetupRecurring();
1326  $setups = array();
1327  $recurrings = array();
1328  $triggereds = array();
1329  foreach ($setupRecurring as $billingId => $value) {
1330  switch($value) {
1331  case "triggered":
1332  $setups[] = prep_save($billingId, 20);
1333  break;
1334  }
1335  }
1336  $triggereds = count($triggereds) == 0 ? false : "'" . implode("', '", $triggereds) . "'";
1337 
1338  $whereOr = array();
1339  $whereOr[] = "fd.billing_status not in (2,3)";
1340 
1341  $complicatedAnd = array();
1342  $complicatedAnd[] = "fd.billing_status in (2,3)"; // Both mean "ready to bill." 3 is if there was supposed to be a workflow attached.
1343 
1344  if ($triggereds !== false) {
1345  $complicatedAnd[] = "si.billing_system_id not in ($triggereds)"; // Only for setups and recurring features.
1346  }
1347 
1348  $complicatedAnd[] = "date_trunc('month', fd.start_date) >= date_trunc('month', now())"; // Feature starts this month or later.
1349 
1350  $complicatedAnd = "(" . implode(") and (", $complicatedAnd) . ")";
1351 
1352  $whereOr[] = $complicatedAnd;
1353  $whereOr = "(" . implode(") or (", $whereOr) . ")";
1354 
1355  return $whereOr;
1356 }
1357 
1358 /**
1359  * function GetWhereForActiveLineItems()
1360  * @return String of the where clause for active line items.
1361  */
1362 function GetWhereForActiveLineItems() {
1363  // Is active status and ends this month or later.
1364  $whereAnd = array();
1365  $whereAnd[] = "fd.billing_status in (2,3)"; // Must be active.
1366  $whereAnd[] = "(fd.end_date is null or date_trunc('month', fd.end_date) >= date_trunc('month', now()))"; // Does not end or ends this month or later.
1367  $whereAnd[] = "date_trunc('month', fd.start_date) < date_trunc('month', now())"; // Feature started before this month.
1368 
1369  $where = "(" . implode(") and (", $whereAnd) . ")";
1370 
1371  return $where;
1372 }
1373 
1374 /**
1375  * function GetWhereForCompletedLineItems()
1376  * @return String of the where clause for completed line items.
1377  */
1378 function GetWhereForCompletedLineItems() {
1379  // Is active status and ends before this month.
1380  $where = "(fd.billing_status in (2,3) and date_trunc('month', fd.end_date) < date_trunc('month', now()))";
1381 
1382  return $where;
1383 }
1384 
1385 /**
1386  * function ReadPendingDetailsTop($dbh, $sysenv, $Hu)
1387  * This gets the values for the pending grid.
1388  * This DOES NOT INCLUDE the details of the pending grid.
1389  *
1390  * @param $dbh -- the database connection.
1391  * @param $sysenv -- the system environment variables.
1392  * @param $Hu -- the logged in user.
1393  *
1394  * @return $status -- "000" if successful, nonzero otherwise.
1395  * @return $error -- "" if successful, nonempty otherwise.
1396  * @return $record -- the records for the grid.
1397  * @return $operation -- "read"
1398  */
1399 function ReadPendingDetailsTop($dbh, $sysenv, $Hu) {
1400  try {
1401  extract($sysenv["BILL"]);
1402  if (!isset($salesOrderId) || trim($salesOrderId) == "") {
1403  throw new exception ("Contract id is required.", 1);
1404  }
1405  $salesOrderId = intval($salesOrderId);
1406  $orderDetailIds = isset($orderDetailIds) ? trim($orderDetailIds) : "[]";
1407  $orderDetailIds = HCU_JsonDecode($orderDetailIds);
1408 
1409  $whereOr = GetWhereForPendingLineItems();
1410 
1411  // Returns all contract details where there are no line items OR there are one or more pending line items.
1412  $sql = "select d.id \"orderDetailId\", trim(coalesce(p.home_cu_desc, d.prod_id)) \"detailDescription\",
1413  trim(d.last_modified_by) \"lastModifiedBy\", d.last_modified_date \"lastModifiedDate\", trim(d.prod_id) \"productId\",
1414  coalesce(cb.pendingcount, 0) \"numDetails\"
1415  from cubillsalesorderdetail d
1416  left join cuprodlist p on d.prod_id = p.home_cu_code
1417  left join (
1418  select count(*), fd.sales_order_detail_id from cubillfeaturedetail fd
1419  group by fd.sales_order_detail_id
1420  ) as ca (allcount, detail_id) on d.id = ca.detail_id
1421  left join (
1422  select count(*), fd.sales_order_detail_id from cubillfeaturedetail fd
1423  inner join cubillsalesitem si on fd.sales_item_id = si.id
1424  where $whereOr
1425  group by fd.sales_order_detail_id
1426  ) as cb (pendingcount, detail_id) on d.id = cb.detail_id
1427  where d.sales_order_id = " . intval($salesOrderId) . " and (coalesce(ca.allcount, 0) = 0 or cb.pendingcount > 0)
1428  order by 1";
1429  $sth = db_query($sql, $dbh);
1430  if (!$sth) {
1431  throw new exception ("Query failed", 1);
1432  }
1433 
1434  $pendingDetails = db_fetch_all($sth);
1435  $pendingDetails = $pendingDetails === false ? array() : $pendingDetails;
1436 
1437  foreach($pendingDetails as $i => $pendingDetail) {
1438  $pendingDetails[$i]["opened"] = in_array($pendingDetail["orderDetailId"], $orderDetailIds) && $pendingDetail["numDetails"] > 0;
1439  }
1440 
1441  $returnArray = array("status" => "000", "error" => "", "record" => $pendingDetails, "operation" => "read");
1442  } catch (exception $e) {
1443  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "record" => array(), "operation" => "read");
1444  }
1445  return $returnArray;
1446 }
1447 
1448 /**
1449  * function ReadActiveDetails($dbh, $sysenv, $Hu)
1450  * This gets the values for the active grid.
1451  *
1452  * @param $dbh -- the database connection.
1453  * @param $sysenv -- the system environment variables.
1454  * @param $Hu -- the logged in user.
1455  *
1456  * @return $status -- "000" if successful, nonzero otherwise.
1457  * @return $error -- "" if successful, nonempty otherwise.
1458  * @return $record -- the records for the grid.
1459  * @return $operation -- "read"
1460  */
1461 function ReadActiveDetails($dbh, $sysenv, $Hu) {
1462  try {
1463  extract($sysenv["BILL"]);
1464  if (!isset($salesOrderId) || trim($salesOrderId) == "") {
1465  throw new exception ("Contract id is required.", 1);
1466  }
1467  $salesOrderId = intval($salesOrderId);
1468 
1469  if (!isset($includeIds) || trim($includeIds) == "") {
1470  throw new exception ("Include ids are required.", 5);
1471  }
1472 
1473  if (!isset($excludeIds) || trim($excludeIds) == "") {
1474  throw new exception ("Exclude ids are required.", 6);
1475  }
1476 
1477  $includeIds = HCU_JsonDecode($includeIds);
1478  $includeIds = array_map("intval", $includeIds);
1479 
1480  $excludeIds = HCU_JsonDecode($excludeIds);
1481  $excludeIds = array_map("intval", $excludeIds);
1482 
1483  $includePhrase = count($includeIds) > 0 ? "or fd.id in (" . implode(", ", $includeIds) . ")" : "";
1484  $excludePhrase = count($excludeIds) > 0 ? "and fd.id not in (" . implode(", ", $excludeIds) . ")" : "";
1485 
1486  $setupRecurring = GetSetupRecurring();
1487 
1488  $where = GetWhereForActiveLineItems();
1489 
1490  $sql = "select trim(coalesce(p.home_cu_desc, d.prod_id)) \"detailDescription\", fd.id \"featureDetailId\", fd.start_date \"startDate\",
1491  fd.end_date \"endDate\", fd.billing_template \"template\",
1492  trim(si.display_name) \"salesItemDescr\", fd.sales_item_id \"salesItemId\", fd.billing_status \"billingStatus\", fd.bill_half_boolean \"billHalf\",
1493  trim(si.billing_system_id) \"billingId\", fd.partially_billed_date \"partiallyBilledDate\", fd.partially_billed_amount \"partiallyBilledAmount\",
1494  fd.frequency \"frequency\", fd.months_to_bill \"billsOn\", trim(d.prod_id) \"feature\"
1495  from cubillfeaturedetail fd
1496  inner join cubillsalesorderdetail d on fd.sales_order_detail_id = d.id
1497  left join cubillsalesitem si on fd.sales_item_id = si.id
1498  left join cuprodlist p on d.prod_id = p.home_cu_code
1499  where d.sales_order_id = " . intval($salesOrderId) . " and ($where $includePhrase) $excludePhrase
1500  order by 1, fd.id";
1501 
1502  $sth = db_query($sql, $dbh);
1503  if (!$sth) {
1504  throw new exception ("Query failed.", 2);
1505  }
1506 
1507  $rawRecords = db_fetch_all($sth);
1508  $rawRecords = $rawRecords === false ? array() : $rawRecords;
1509  $featureRecords = array();
1510 
1511  foreach($rawRecords as $record) {
1512 
1513  $featureRecord = array("featureDetailId" => $record["featureDetailId"]);
1514 
1515  $template = HCU_JsonDecode($record["template"]);
1516 
1517  $fixed = floatval($template["fixed"]);
1518  $variable = floatval($template["variable"]);
1519  $qty1 = intval($template["qty1"]);
1520  $qty2 = intval($template["qty2"]);
1521  $aDNID = HCU_array_key_exists("aDNID", $template) ? $template["aDNID"] : "";
1522 
1523  $featureRecord["featureDescription"] = GetTextDescription($record["salesItemId"], $record["billingId"], $fixed,
1524  $variable, $qty1, $qty2, $aDNID);
1525  $featureRecord["featureName"] = $template["descr"] == null || $template["descr"] == "" ? $record["salesItemDescr"] : $template["descr"];
1526  $featureRecord["productName"] = $record["detailDescription"];
1527 
1528  $billingStatus = intval($record["billingStatus"]);
1529  $setupLabel = HCU_array_key_value($record["billingId"], $setupRecurring);
1530  $setup = $setupLabel !== false ? $setupLabel == "setup" : false;
1531 
1532  $results = GetBillingStatusLabel($billingStatus, $record["startDate"], $record["endDate"], $setupLabel, "active");
1533  if ($results["status"] !== "000") {
1534  throw new exception ("Get billing status label failed.", 3);
1535  }
1536 
1537  $featureRecord["status"] = $results["data"]["statusLabel"];
1538 
1539  $results = GetHalfBillingStatus($setup, $record["billHalf"], $record["partiallyBilledDate"], $record["startDate"]);
1540  if ($results["status"] !== "000") {
1541  throw new exception ("Get half billing status failed.", 4);
1542  }
1543  $featureRecord["partialBillingStatus"] = $results["data"]["statusLabel"];
1544 
1545  $results = GetFrequencyLabel(intval($record["billsOn"]), $setupLabel);
1546  $featureRecord["frequencyDescr"] = $results["data"]["frequencyLabel"];
1547 
1548  $showCompleteButton = false;
1549  $completeButtonText = "";
1550 
1551  if ($setupLabel !== "triggered") {
1552  $showCompleteButton = true;
1553 
1554  // You can UNDO it if it is going to be completed this month or next.
1555  if (in_array($featureRecord["status"], array("Ending next month", "Ending this month"))) {
1556  $completedButtonText = "Undo";
1557  // You can also UNDO it if this session, you specified an end date.
1558  // (This catches the case where the end date is farther in the future than next month.)
1559  } else if (in_array($featureRecord["featureDetailId"], $includeIds) && $record["endDate"] != null) {
1560  $completeButtonText = "Undo";
1561 
1562  $endDateTime = new DateTime($record["endDate"]);
1563  $featureRecord["status"] = "Ending " . $endDateTime->format("m/Y");
1564  } else {
1565  $completeButtonText = "Complete";
1566  }
1567  }
1568 
1569  $featureRecord["showCompleteButton"] = $showCompleteButton;
1570  $featureRecord["completeButtonText"] = $completeButtonText;
1571  $featureRecord["endDate"] = $record["endDate"];
1572 
1573  $featureRecords[] = $featureRecord;
1574  }
1575 
1576  $returnArray = array("status" => "000", "error" => "", "record" => $featureRecords, "operation" => "read");
1577  } catch (exception $e) {
1578  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "record" => array(), "operation" => "read");
1579  }
1580  return $returnArray;
1581 }
1582 
1583 /**
1584  * function ReadCompletedDetails($dbh, $sysenv, $Hu)
1585  * This gets the values for the completed grid.
1586  *
1587  * @param $dbh -- the database connection.
1588  * @param $sysenv -- the system environment variables.
1589  * @param $Hu -- the logged in user.
1590  *
1591  * @return $status -- "000" if successful, nonzero otherwise.
1592  * @return $error -- "" if successful, nonempty otherwise.
1593  * @return $record -- the records for the grid.
1594  * @return $operation -- "read"
1595  */
1596 function ReadCompletedDetails($dbh, $sysenv, $Hu) {
1597  try {
1598  extract($sysenv["BILL"]);
1599  if (!isset($salesOrderId) || trim($salesOrderId) == "") {
1600  throw new exception ("Contract id is required.", 1);
1601  }
1602  $salesOrderId = intval($salesOrderId);
1603 
1604  if (!isset($includeIds) || trim($includeIds) == "") {
1605  throw new exception ("Include ids are required.", 5);
1606  }
1607 
1608  if (!isset($excludeIds) || trim($excludeIds) == "") {
1609  throw new exception ("Exclude ids are required.", 6);
1610  }
1611 
1612  $includeIds = HCU_JsonDecode($includeIds);
1613  $includeIds = array_map("intval", $includeIds);
1614 
1615  $excludeIds = HCU_JsonDecode($excludeIds);
1616  $excludeIds = array_map("intval", $excludeIds);
1617 
1618  $includePhrase = count($includeIds) > 0 ? "or fd.id in (" . implode(", ", $includeIds) . ")" : "";
1619  $excludePhrase = count($excludeIds) > 0 ? "and fd.id not in (" . implode(", ", $excludeIds) . ")" : "";
1620 
1621  $setupRecurring = GetSetupRecurring();
1622 
1623  $where = GetWhereForCompletedLineItems();
1624 
1625  $sql = "select trim(coalesce(p.home_cu_desc, d.prod_id)) \"detailDescription\", fd.id \"featureDetailId\", fd.start_date \"startDate\",
1626  fd.end_date \"endDate\", fd.billing_template \"template\",
1627  trim(si.display_name) \"salesItemDescr\", fd.sales_item_id \"salesItemId\", fd.billing_status \"billingStatus\", fd.bill_half_boolean \"billHalf\",
1628  trim(si.billing_system_id) \"billingId\", fd.partially_billed_date \"partiallyBilledDate\", fd.partially_billed_amount \"partiallyBilledAmount\",
1629  fd.frequency \"frequency\", fd.months_to_bill \"billsOn\", trim(d.prod_id) \"feature\"
1630  from cubillfeaturedetail fd
1631  inner join cubillsalesorderdetail d on fd.sales_order_detail_id = d.id
1632  left join cubillsalesitem si on fd.sales_item_id = si.id
1633  left join cuprodlist p on d.prod_id = p.home_cu_code
1634  where d.sales_order_id = " . intval($salesOrderId) . " and ($where $includePhrase) $excludePhrase
1635  order by 1, fd.id";
1636 
1637  $sth = db_query($sql, $dbh);
1638  if (!$sth) {
1639  throw new exception ("Query failed.", 2);
1640  }
1641 
1642  $rawRecords = db_fetch_all($sth);
1643  $rawRecords = $rawRecords === false ? array() : $rawRecords;
1644  $featureRecords = array();
1645 
1646  foreach($rawRecords as $record) {
1647 
1648  $featureRecord = array("featureDetailId" => $record["featureDetailId"]);
1649 
1650  $template = HCU_JsonDecode($record["template"]);
1651 
1652  $fixed = floatval($template["fixed"]);
1653  $variable = floatval($template["variable"]);
1654  $qty1 = intval($template["qty1"]);
1655  $qty2 = intval($template["qty2"]);
1656  $aDNID = HCU_array_key_exists("aDNID", $template) ? $template["aDNID"] : "";
1657 
1658  $featureRecord["featureDescription"] = GetTextDescription($record["salesItemId"], $record["billingId"], $fixed,
1659  $variable, $qty1, $qty2, $aDNID);
1660  $featureRecord["featureName"] = $template["descr"] == null || $template["descr"] == "" ? $record["salesItemDescr"] : $template["descr"];
1661  $featureRecord["productName"] = $record["detailDescription"];
1662 
1663  $billingStatus = intval($record["billingStatus"]);
1664  $setupLabel = HCU_array_key_value($record["billingId"], $setupRecurring);
1665  $setup = $setupLabel !== false ? $setupLabel == "setup" : false;
1666 
1667  $results = GetBillingStatusLabel($billingStatus, $record["startDate"], $record["endDate"], $setupLabel, "completed");
1668  if ($results["status"] !== "000") {
1669  throw new exception ("Get billing status label failed.", 3);
1670  }
1671 
1672  $featureRecord["status"] = $results["data"]["statusLabel"];
1673 
1674  $results = GetHalfBillingStatus($setup, $record["billHalf"], $record["partiallyBilledDate"], $record["startDate"]);
1675  if ($results["status"] !== "000") {
1676  throw new exception ("Get half billing status failed.", 4);
1677  }
1678  $featureRecord["partialBillingStatus"] = $results["data"]["statusLabel"];
1679 
1680  $results = GetFrequencyLabel(intval($record["billsOn"]), $setupLabel);
1681  $featureRecord["frequencyDescr"] = $results["data"]["frequencyLabel"];
1682 
1683  $showAdvanceWorkflow = false;
1684  $advanceWorkflowText = "";
1685 
1686  if ($featureRecord["status"] == "Completed") {
1687  $showAdvanceWorkflow = true;
1688  $advanceWorkflowText = "Reactivate";
1689  } else {
1690  $showAdvanceWorkflow = true;
1691  $advanceWorkflowText = "Undo";
1692  }
1693 
1694  $featureRecord["showAdvanceWorkflow"] = $showAdvanceWorkflow;
1695  $featureRecord["advanceWorkflowText"] = $advanceWorkflowText;
1696 
1697  $featureRecords[] = $featureRecord;
1698  }
1699 
1700  $returnArray = array("status" => "000", "error" => "", "record" => $featureRecords, "operation" => "read");
1701  } catch (exception $e) {
1702  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "record" => array(), "operation" => "read");
1703  }
1704  return $returnArray;
1705 }
1706 
1707 /**
1708  * function ReadPendingDetailsBottom($dbh, $sysenv, $Hu)
1709  * This gets the values for the detail grid.
1710  * This is for only one detail in the pending grid.
1711  * Each detail grid will call this function with the orderDetailId.
1712  *
1713  * @param $dbh -- the database connection.
1714  * @param $sysenv -- the system environment variables.
1715  * @param $Hu -- the logged in user.
1716  *
1717  * @return $status -- "000" if successful, nonzero otherwise.
1718  * @return $error -- "" if successful, nonempty otherwise.
1719  * @return $record -- the records for the grid.
1720  * @return $operation -- "read"
1721  */
1722 function ReadPendingDetailsBottom($dbh, $sysenv, $Hu) {
1723  try {
1724  extract($sysenv["BILL"]);
1725  if (!isset($orderDetailId) || trim($orderDetailId) == "") {
1726  throw new exception ("Detail id is required.", 1);
1727  }
1728  $orderDetailId = intval($orderDetailId);
1729 
1730  if (!isset($includeIds) || trim($includeIds) == "") {
1731  throw new exception ("Include ids are required.", 5);
1732  }
1733 
1734  if (!isset($excludeIds) || trim($excludeIds) == "") {
1735  throw new exception ("Exclude ids are required.", 6);
1736  }
1737 
1738  $includeIds = HCU_JsonDecode($includeIds);
1739  $includeIds = array_map("intval", $includeIds);
1740 
1741  $excludeIds = HCU_JsonDecode($excludeIds);
1742  $excludeIds = array_map("intval", $excludeIds);
1743 
1744  $includePhrase = count($includeIds) > 0 ? "or fd.id in (" . implode(", ", $includeIds) . ")" : "";
1745  $excludePhrase = count($excludeIds) > 0 ? "and fd.id not in (" . implode(", ", $excludeIds) . ")" : "";
1746 
1747  $whereOr = GetWhereForPendingLineItems();
1748 
1749  $setupRecurring = GetSetupRecurring();
1750 
1751  $sql = "select fd.id \"featureDetailId\", fd.start_date \"startDate\", fd.end_date \"endDate\", fd.billing_template \"template\",
1752  trim(si.display_name) \"salesItemDescr\", fd.sales_item_id \"salesItemId\", fd.billing_status \"billingStatus\", fd.bill_half_boolean \"billHalf\",
1753  trim(si.billing_system_id) \"billingId\",
1754  fd.partially_billed_date \"partiallyBilledDate\",
1755  exists (select 'FOUND' from cubillpreinvoice pi where fd.id = pi.feature_detail_id) \"linkExists\",
1756  exists (select 'FOUND' from cutrack t where fd.id = t.billing_feature_id) \"jobExists\", fd.partially_billed_amount \"partiallyBilledAmount\",
1757  fd.frequency \"frequency\", fd.months_to_bill \"billsOn\"
1758  from cubillfeaturedetail fd
1759  left join cubillsalesitem si on fd.sales_item_id = si.id
1760  where fd.sales_order_detail_id = " . intval($orderDetailId) . "
1761  and ($whereOr $includePhrase) $excludePhrase
1762  order by fd.id";
1763 
1764  $sth = db_query($sql, $dbh);
1765  if (!$sth) {
1766  throw new exception ("Query failed.", 2);
1767  }
1768 
1769  $rawRecords = db_fetch_all($sth);
1770  $rawRecords = $rawRecords === false ? array() : $rawRecords;
1771  $featureRecords = array();
1772 
1773  foreach($rawRecords as $record) {
1774 
1775  $featureRecord = array("featureDetailId" => $record["featureDetailId"]);
1776 
1777  $template = HCU_JsonDecode($record["template"]);
1778 
1779  $fixed = floatval($template["fixed"]);
1780  $variable = floatval($template["variable"]);
1781  $qty1 = intval($template["qty1"]);
1782  $qty2 = intval($template["qty2"]);
1783  $aDNID = HCU_array_key_exists("aDNID", $template) ? $template["aDNID"] : "";
1784 
1785  $featureRecord["actualDescription"] = $template["descr"] == null || $template["descr"] == "" ? $record["salesItemDescr"] : $template["descr"];
1786  $featureRecord["textDescription"] = GetTextDescription($record["salesItemId"], $record["billingId"], $fixed,
1787  $variable, $qty1, $qty2, $aDNID);
1788 
1789  $billingStatus = intval($record["billingStatus"]);
1790  $setupLabel = HCU_array_key_value($record["billingId"], $setupRecurring);
1791  $setup = $setupLabel !== false ? $setupLabel == "setup" : false;
1792 
1793  $results = GetBillingStatusLabel($billingStatus, $record["startDate"], $record["endDate"], $setupLabel, "pending");
1794  if ($results["status"] !== "000") {
1795  throw new exception ("Get billing status label failed.", 3);
1796  }
1797 
1798  $featureRecord["billingStatusLabel"] = $results["data"]["statusLabel"];
1799 
1800  $results = GetHalfBillingStatus($setup, $record["billHalf"], $record["partiallyBilledDate"], $record["startDate"]);
1801  if ($results["status"] !== "000") {
1802  throw new exception ("Get half billing status failed.", 4);
1803  }
1804  $featureRecord["partialBillingStatus"] = $results["data"]["statusLabel"];
1805 
1806  $results = GetFrequencyLabel(intval($record["billsOn"]), $setupLabel);
1807  $featureRecord["frequencyDescr"] = $results["data"]["frequencyLabel"];
1808 
1809  if ($record["linkExists"] == "t" || $record["jobExists"] == "t") {
1810  $featureRecord["deletable"] = false;
1811  } else {
1812  if ($record["billingStatus"] == 2) {
1813  $dateTime = new DateTime ($record["startDate"]);
1814  $firstOfMonth = new DateTime();
1815  $firstOfMonth->modify("first day of this month");
1816  $interval = $dateTime->diff($firstOfMonth);
1817  $dateDiff = $interval->format('%R%a');
1818  if ($dateDiff <= 0) {
1819  $featureRecord["deletable"] = true;
1820  } else {
1821  $featureRecord["deletable"] = false;
1822  }
1823  } else {
1824  $featureRecord["deletable"] = true;
1825  }
1826  }
1827 
1828  $showAdvanceWorkflow = false;
1829  $advanceWorkflowText = "";
1830 
1831  if (in_array($featureRecord["billingStatusLabel"],
1832  array("Not triggered", "Pending", "Pending Workflow", "Pending Workflow, Billing Half", "No Workflow"))) {
1833  switch($setupLabel) {
1834  case "recurring":
1835  $showAdvanceWorkflow = true;
1836  $advanceWorkflowText = "Start";
1837  break;
1838  case "triggered":
1839  $showAdvanceWorkflow = true;
1840  $advanceWorkflowText = "Trigger";
1841  break;
1842  }
1843  } else if ($featureRecord["billingStatusLabel"] !== "Ready to Bill Half") {
1844  switch($setupLabel) {
1845  case "recurring":
1846  $showAdvanceWorkflow = true;
1847  $advanceWorkflowText = "Undo";
1848  break;
1849  case "triggered":
1850  $showAdvanceWorkflow = true;
1851  $advanceWorkflowText = "Undo Trigger";
1852  break;
1853  case "setup":
1854  $showAdvanceWorkflow = true;
1855  $advanceWorkflowText = "Change Start";
1856  break;
1857  }
1858  }
1859 
1860  $featureRecord["showAdvanceWorkflow"] = $showAdvanceWorkflow;
1861  $featureRecord["advanceWorkflowText"] = $advanceWorkflowText;
1862 
1863  $featureRecord["startDate"] = $record["startDate"];
1864 
1865  $featureRecords[] = $featureRecord;
1866  }
1867 
1868  $returnArray = array("status" => "000", "error" => "", "record" => $featureRecords, "operation" => "read");
1869  } catch (exception $e) {
1870  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "record" => array(), "operation" => "read");
1871  }
1872  return $returnArray;
1873 }
1874 
1875 /**
1876  * function ReadSalesOrderLineItem($dbh, $sysenv, $Hu, $isActive)
1877  * This gets all the data need to add/update a line item.
1878  * For the most part, the detail grid call ONLY returns the display labels.
1879  *
1880  * @param $dbh -- the database connection.
1881  * @param $sysenv -- the system environment variables.
1882  * @param $Hu -- the logged in user.
1883  * @param $isActive -- this can be called from the active grid.
1884  * In this case, workflow, bill half, and frequency values cannot be changed.
1885  *
1886  * @return $status -- "000" if successful, nonzero otherwise.
1887  * @return $error -- "" if successful, nonempty otherwise.
1888  * @return $lineItemData -- this contains all the values of the line item.
1889  * @return $configuration -- this contains the labels and what to show for the template.
1890  * @return $bindOptions -- this contains dropdownlist values to select.
1891  * For any DIVs that can be changed, the visibility of those DIVs are shown here.
1892  * That is for bill half, frequency, and workflow.
1893  * @return $ddls.workflowUnattachedJobs -- a list of workflows not attached for that billing system id.
1894  * @return $ddls.workflows -- a list of workflow options for the line item.
1895  * This starts with the default workflow option list.
1896  * If there are no unattached jobs, then the option to associate it to a job is eliminated.
1897  */
1898 function ReadSalesOrderLineItem($dbh, $sysenv, $Hu, $isActive) {
1899  try {
1900  extract($sysenv["BILL"]);
1901  if (!isset($featureDetailId) || trim($featureDetailId) == "") {
1902  throw new exception ("Line item id is required.", 1);
1903  }
1904 
1905  $sql = "select fd.id \"featureDetailId\", fd.start_date \"startDate\", fd.end_date \"endDate\",
1906  fd.billing_template \"template\", si.display_name \"salesItemDescr\", fd.sales_item_id \"salesItemId\",
1907  fd.billing_status \"billingStatus\", fd.bill_half_boolean \"billHalf\", si.billing_system_id \"billingId\",
1908  fd.partially_billed_date \"partiallyBilledDate\",
1909  exists (select 'FOUND' from cubillpreinvoice pi where fd.id = pi.feature_detail_id) \"linkExists\",
1910  exists (select 'FOUND' from cutrack t where fd.id = t.billing_feature_id) \"jobExists\",
1911  fd.partially_billed_amount \"partiallyBilledAmount\", fd.frequency \"frequency\", fd.months_to_bill \"billsOn\"
1912  from cubillfeaturedetail fd
1913  left join cubillsalesitem si on fd.sales_item_id = si.id
1914  where fd.id = " . intval($featureDetailId);
1915 
1916  $sth = db_query($sql, $dbh);
1917  if (!$sth) {
1918  throw new exception ("Query failed.", 2);
1919  }
1920 
1921  $rawRecord = db_num_rows($sth) > 0 ? db_fetch_assoc($sth, 0) : array();
1922 
1923  $featureRecord = array("featureDetailId" => $rawRecord["featureDetailId"], "startDate" => $rawRecord["startDate"], "endDate" => $rawRecord["endDate"],
1924  "salesItemId" => intval($rawRecord["salesItemId"]),
1925  "billingStatus" => intval($rawRecord["billingStatus"]), "partiallyBilledAmount" => floatval($rawRecord["partiallyBilledAmount"]),
1926  "partiallyBilledDate" => $rawRecord["partiallyBilledDate"]);
1927 
1928  $template = HCU_JsonDecode($rawRecord["template"]);
1929  $featureRecord["actualDescription"] = $template["descr"] == null || $template["descr"] == "" ? $rawRecord["salesItemDescr"] : $template["descr"];
1930  $featureRecord["overrideDescription"] = $template["descr"] == null || $template["descr"] == "" ? "" : $template["descr"];
1931  $featureRecord["fixed"] = floatval($template["fixed"]);
1932  $featureRecord["variable"] = floatval($template["variable"]);
1933  $featureRecord["qty1"] = intval($template["qty1"]);
1934  $featureRecord["qty2"] = intval($template["qty2"]);
1935  // Positive infinity is marked as null in the application (with a placeholder) and -1 in the database.
1936  $featureRecord["qty2"] = $featureRecord["qty2"] == -1 ? null : $featureRecord["qty2"];
1937  $featureRecord["aDNID"] = HCU_array_key_exists("aDNID", $template) ? $template["aDNID"] : "";
1938 
1939  $bindOptions = GetDefaultLineItemBindOptions();
1940 
1941  if ($rawRecord["billHalf"] === "Y") {
1942  $bindOptions["billHalfValue"] = "future";
1943  $bindOptions["billHalfCheckReceivedRowVisible"] = false;
1944  $bindOptions["billHalfDateRowVisible"] = true;
1945  $bindOptions["billHalfAmountRowVisible"] = false;
1946  $bindOptions["billHalfDateLabel"] = "Date to Bill Half:";
1947  } else if (isset($rawRecord["partiallyBilledDate"])) {
1948  $bindOptions["billHalfValue"] = "partial";
1949  $bindOptions["billHalfCheckReceivedRowVisible"] = true;
1950  $bindOptions["billHalfDateRowVisible"] = false;
1951  $bindOptions["billHalfAmountRowVisible"] = true;
1952  $bindOptions["billHalfDateLabel"] = "Check Received:";
1953  }
1954 
1955  if ($rawRecord["frequency"] !== 0) {
1956  $billsOn1Value = -1;
1957  $billsOn2Value = -1;
1958  $billsOn = $rawRecord["billsOn"];
1959 
1960  for ($i = 0; $i != 12; $i++) {
1961  if ($billsOn1Value == -1) {
1962  $monthSet = $billsOn & (1 << $i);
1963 
1964  if ($monthSet !== 0) {
1965  $billsOn1Value = $i;
1966  }
1967  } else if ($billsOn2Value == -1) {
1968  $monthSet = $billsOn & (1 << $i);
1969 
1970  if ($monthSet !== 0) {
1971  $billsOn2Value = $i;
1972  }
1973  } else {
1974  break;
1975  }
1976  }
1977 
1978  switch($rawRecord["frequency"]) {
1979  case 1:
1980  $bindOptions["frequencyValue"] = 1;
1981  $bindOptions["billsOn1RowVisible"] = true;
1982  $bindOptions["billsOn2RowVisible"] = true;
1983  $bindOptions["billsOn1Value"] = $billsOn1Value;
1984  $bindOptions["billsOn2Value"] = $billsOn2Value;
1985  break;
1986  case 2:
1987  $bindOptions["frequencyValue"] = 2;
1988  $bindOptions["billsOn1RowVisible"] = true;
1989  $bindOptions["billsOn2RowVisible"] = false;
1990  $bindOptions["billsOn1Value"] = $billsOn1Value;
1991  break;
1992  default:
1993  break;
1994  }
1995  }
1996 
1997 
1998  $billingId = $rawRecord["billingId"];
1999  $salesItemId = $rawRecord["salesItemId"];
2000 
2001  $results = CompileLineItemTemplateBooleans($billingId, $salesItemId, $rawRecord["jobExists"] === "t");
2002  if ($results["status"] !== "000") {
2003  throw new exception ("Configuration failed.", 3);
2004  }
2005 
2006  $configuration = $results["templateBooleans"];
2007 
2008  if ($configuration["showQuantityRangeRow"]) {
2009  $bindOptions["qtyRangeVisible"] = true; // Needed to save infinity correctly.
2010  }
2011 
2012  $setupConfig = GetSetupRecurring();
2013 
2014  if (HCU_array_key_exists($billingId, $setupConfig)) {
2015  $setupLabel = $setupConfig[$billingId];
2016  } else {
2017  $setupLabel = null;
2018  }
2019 
2020  $workflowUnattachedJobs = array();
2021  $workflowDDL = array();
2022 
2023  if ($setupLabel === "setup" && !$isActive) {
2024  $sql = "select t.issue \"text\", t.track_id \"value\"
2025  from cubillfeaturedetail fd
2026  inner join cubillsalesorderdetail sod on fd.id = " . intval($featureDetailId) . " and fd.sales_order_detail_id = sod.id
2027  inner join cuissues i on sod.prod_id = i.gen_for_product
2028  inner join cubillsalesorder so on sod.sales_order_id = so.id
2029  inner join cutrack t on i.issue_id = t.issue_id and trim(lower(so.cu_id)) = trim(lower(t.user_name)) and t.billing_feature_id = 0
2030  order by t.issue";
2031  $sth = db_query($sql, $dbh);
2032 
2033  if (!$sth) {
2034  throw new exception ("Query failed.", 4);
2035  }
2036 
2037  $workflowUnattachedJobs = db_fetch_all($sth);
2038  $workflowUnattachedJobs = $workflowUnattachedJobs === false ? array() : $workflowUnattachedJobs;
2039 
2040  $workflowDDL = GetWorkflowOptions();
2041 
2042  if (count($workflowUnattachedJobs) === 0) {
2043  $newWorkflowDDL = array();
2044  foreach($workflowDDL as $record) {
2045 
2046  if ($record["value"] !== "associate") {
2047  $newWorkflowDDL[] = $record;
2048  }
2049  }
2050 
2051  $workflowDDL = $newWorkflowDDL;
2052  } else {
2053  $bindOptions["unattachedJobValue"] = intval($workflowUnattachedJobs[0]["value"]);
2054  }
2055  }
2056 
2057  if ($isActive) {
2058  $configuration["showOverrideRow"] = false;
2059  $configuration["showBillHalfDiv"] = false;
2060  $configuration["showWorkflowDiv"] = false;
2061  $configuration["showFrequencyDiv"] = false;
2062  } else {
2063  $configuration["showOverrideRow"] = true;
2064  }
2065 
2066  $ddls = array("workflowUnattachedJobs" => $workflowUnattachedJobs, "workflows" => $workflowDDL);
2067 
2068  $returnArray = array("status" => "000", "error" => "", "lineItemData" => $featureRecord, "configuration" => $configuration,
2069  "bindOptions" => $bindOptions, "ddls" => $ddls, "type" => "read");
2070 
2071  } catch (exception $e) {
2072  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "type" => "read");
2073  }
2074  return $returnArray;
2075 }
2076 
2077 /**
2078  * function GetDefaultLineItemBindOptions()
2079  * @return the initial dropdownlist and visibility options.
2080  */
2081 function GetDefaultLineItemBindOptions() {
2082  $bindOptions = array();
2083 
2084  $bindOptions["isOverrideCheckboxShown"] = true;
2085  $bindOptions["isOverrideDescrShown"] = false;
2086  $bindOptions["qtyRangeVisible"] = false;
2087 
2088  // BillHalf
2089  $bindOptions["billHalfValue"] = "full";
2090  $bindOptions["billHalfCheckReceivedRowVisible"] = false;
2091  $bindOptions["billHalfDateRowVisible"] = false;
2092  $bindOptions["billHalfAmountRowVisible"] = false;
2093  $bindOptions["billHalfDateLabel"] = "";
2094  $bindOptions["billHalfDDLEnabled"] = true;
2095 
2096  // Workflow
2097  $bindOptions["workflowValue"] = "create";
2098  $bindOptions["workflowRowVisible"] = true;
2099  $bindOptions["associateToJobRowVisible"] = false;
2100  $bindOptions["immediateBillRowVisible"] = false;
2101  $bindOptions["unattachedJobValue"] = -1;
2102 
2103  // Frequency
2104  $bindOptions["frequencyValue"] = 0;
2105  $bindOptions["billsOn1RowVisible"] = false;
2106  $bindOptions["billsOn2RowVisible"] = false;
2107  $bindOptions["billsOn1Value"] = 0;
2108  $bindOptions["billsOn2Value"] = 0;
2109 
2110  return $bindOptions;
2111 }
2112 
2113 /**
2114  * function GetDefaultProductOptions($ddls)
2115  * @param $ddls -- the current dropdownlist values.
2116  * (To set the initial values to the first in the lists.)
2117  * @return the initial dropdownlist and visibility options.
2118  */
2119 function GetDefaultProductOptions($ddls) {
2120  $bindOptions = array();
2121  $source = array();
2122 
2123  $bindOptions["productWarningVisible"] = false;
2124  $bindOptions["configRowVisible"] = false;
2125  $bindOptions["billHalfRowVisible"] = false;
2126  $bindOptions["billHalfCheckReceivedRowVisible"] = false;
2127  $bindOptions["billHalfDateRowVisible"] = false;
2128  $bindOptions["workflowRowVisible"] = false;
2129  $bindOptions["associateToJobRowVisible"] = false;
2130  $bindOptions["immediateBillRowVisible"] = false;
2131  $bindOptions["dnidRowVisible"] = false;
2132  $bindOptions["billHalfDDLEnabled"] = true;
2133  $bindOptions["productId"] = "";
2134  $bindOptions["productOption"] = "";
2135  $bindOptions["workflowValue"] = "none";
2136  $bindOptions["billHalfValue"] = "full";
2137  $bindOptions["unattachedJobValue"] = 0;
2138  $bindOptions["hasSetup"] = false;
2139 
2140  $source["aDNID"] = "";
2141  $source["partiallyBilledDate"] = null;
2142 
2143  $productItem = count($ddls["productItems"]) > 0 ? $ddls["productItems"][0] : null;
2144 
2145  if ($productItem != null) {
2146 
2147  if (HCU_array_key_value("hasOptions", $productItem)) {
2148  $bindOptions["configRowVisible"] = true;
2149  $optionItem = $ddls["productOptions"][0];
2150  $bindOptions["productOption"] = $optionItem["value"];
2151 
2152  if (HCU_array_key_value("hasSetup", $optionItem)) {
2153  $bindOptions["billHalfRowVisible"] = true;
2154  $bindOptions["workflowRowVisible"] = true;
2155  $bindOptions["workflowValue"] = "create";
2156  $bindOptions["hasSetup"] = true;
2157  }
2158  } else if (HCU_array_key_value("hasSetup", $productItem)) {
2159  $bindOptions["billHalfRowVisible"] = true;
2160  $bindOptions["workflowRowVisible"] = true;
2161  $bindOptions["workflowValue"] = "create";
2162  $bindOptions["hasSetup"] = true;
2163  }
2164 
2165  if (HCU_array_key_value("useDNID", $productItem)) {
2166  $bindOptions["dnidRowVisible"] = true;
2167  }
2168 
2169  if (HCU_array_key_value("productExists", $productItem)) {
2170  $bindOptions["productWarningVisible"] = true;
2171  $bindOptions["billHalfValue"] = "nosetup";
2172  $bindOptions["workflowRowVisible"] = false;
2173  $bindOptions["workflowValue"] = "none";
2174  }
2175 
2176  $bindOptions["productId"] = $productItem["value"];
2177  }
2178 
2179  return array("bindOptions" => $bindOptions, "source" => $source);
2180 }
2181 
2182 /**
2183  * function CompileLineItemTemplateBooleans($billingId, $salesItemId, $jobExists)
2184  * This compiles all the label and configuration options into what the template can understand.
2185  *
2186  * @param $billingId -- the billing system id of the line item.
2187  * @param $salesItemId -- the sales item id of the line item.
2188  * @param $jobExists -- true if there exists a workflow for the line item.
2189  *
2190  * @return $status -- "000" if successful, nonzero otherwise.
2191  * @return $error -- "" if successful, nonempty otherwise.
2192  * @return $templateBooleans -- a list of configuration booleans & labels.
2193  */
2194 function CompileLineItemTemplateBooleans($billingId, $salesItemId, $jobExists) {
2195  try {
2196  // Get values from the configuration.
2197  $setupConfig = GetSetupRecurring();
2198  $quantityLabelTypeVariance = GetQuantityLabelTypeVariance();
2199  $quantityLabelType = GetQuantityLabelType();
2200  $quantityLabelsVariance = GetQuantityLabelsVariance();
2201  $quantityLabels = GetQuantityLabels();
2202  $fixedVariableLabelTypeVariance = GetFixedVariableLabelTypeVariance();
2203  $fixedVariableLabelType = GetFixedVariableLabelType();
2204  $fixedVariableLabels = GetFixedVariableLabels();
2205  $fixedVariableLabelsVariance = GetFixedVariableLabelsVariance();
2206  $useDNID = GetUseDNID();
2207  $decimals = GetSaveDigits();
2208  $fullQuantityLabelTypes = GetFullLabelTypes();
2209 
2210  if (HCU_array_key_exists($billingId, $setupConfig)) {
2211  $setupLabel = $setupConfig[$billingId];
2212  } else {
2213  $setupLabel = null;
2214  }
2215 
2216  if (HCU_array_key_exists($billingId, $quantityLabelTypeVariance) && HCU_array_key_exists($salesItemId, $quantityLabelTypeVariance[$billingId])) {
2217  $quantityLabelType = $quantityLabelTypeVariance[$billingId][$salesItemId];
2218  } else if (HCU_array_key_exists($billingId, $quantityLabelType)) {
2219  $quantityLabelType = $quantityLabelType[$billingId];
2220  } else {
2221  $quantityLabelType = null;
2222  }
2223 
2224  if (HCU_array_key_exists($billingId, $quantityLabelsVariance) && HCU_array_key_exists($salesItemId, $quantityLabelsVariance[$billingId])) {
2225  $quantityLabels = $quantityLabelsVariance[$billingId][$salesItemId];
2226  } else if (HCU_array_key_exists($billingId, $quantityLabels)) {
2227  $quantityLabels = $quantityLabels[$billingId];
2228  } else {
2229  $quantityLabels = null;
2230  }
2231 
2232  if (HCU_array_key_exists($billingId, $fixedVariableLabelTypeVariance)
2233  && HCU_array_key_exists($salesItemId, $fixedVariableLabelTypeVariance[$billingId])) {
2234  $fixedVariableLabelType = $fixedVariableLabelTypeVariance[$billingId][$salesItemId];
2235  } else if (HCU_array_key_exists($billingId, $fixedVariableLabelType)) {
2236  $fixedVariableLabelType = $fixedVariableLabelType[$billingId];
2237  } else {
2238  $fixedVariableLabelType = null;
2239  }
2240 
2241  if (HCU_array_key_exists($billingId, $fixedVariableLabelsVariance) && HCU_array_key_exists($salesItemId, $fixedVariableLabelsVariance[$billingId])) {
2242  $fixedVariableLabels = $fixedVariableLabelsVariance[$billingId][$salesItemId];
2243  } else if (HCU_array_key_exists($billingId, $fixedVariableLabels)) {
2244  $fixedVariableLabels = $fixedVariableLabels[$billingId];
2245  } else {
2246  $fixedVariableLabels = null;
2247  }
2248 
2249  $useDNID = HCU_array_key_value($billingId, $useDNID);
2250  $decimals = HCU_array_key_value($billingId, $decimals);
2251  $decimals = $decimals === false ? 2 : intval($decimals); // Default is to use 2 decimals.
2252 
2253  // Compile boolean variables for the template
2254  $templateBooleans = array();
2255 
2256  $templateBooleans["showDNIDDiv"] = $setupLabel === "recurring" && $useDNID;
2257  $templateBooleans["decimals"] = $decimals;
2258  $templateBooleans["format"] = "{0:c" . $decimals . "}";
2259  $templateBooleans["showBillHalfDiv"] = $setupLabel === "setup";
2260  $templateBooleans["showWorkflowDiv"] = $setupLabel === "setup" && !$jobExists;
2261 
2262  $templateBooleans["showQuantity1Row"] = false;
2263  $templateBooleans["showQuantity2Row"] = false;
2264  $templateBooleans["showQuantityRangeRow"] = false;
2265  $templateBooleans["quantity1Label1"] = "";
2266  $templateBooleans["quantity1Label2"] = "";
2267  $templateBooleans["quantity2Label1"] = "";
2268  $templateBooleans["quantity2Label2"] = "";
2269  $templateBooleans["quantityRangeLabel1"] = "";
2270 
2271  if ($setupLabel === "recurring") {
2272  switch($quantityLabelType) {
2273 
2274  case "regular":
2275  $templateBooleans["showQuantity1Row"] = true;
2276  $templateBooleans["showQuantity2Row"] = true;
2277 
2278  if ($quantityLabels == null) {
2279  $templateBooleans["quantity1Label1"] = "Quantity 1:";
2280  $templateBooleans["quantity2Label1"] = "Quantity 2:";
2281  } else {
2282  $templateBooleans["quantity1Label1"] = $quantityLabels[0];
2283  $templateBooleans["quantity2Label1"] = $quantityLabels[1];
2284  }
2285  break;
2286  case "none":
2287  break;
2288  case "no qty1":
2289  $templateBooleans["showQuantity2Row"] = true;
2290 
2291  if ($quantityLabels == null) {
2292  $templateBooleans["quantity2Label1"] = "Quantity 2:";
2293  } else {
2294  $templateBooleans["quantity2Label1"] = $quantityLabels[0];
2295  $templateBooleans["quantity2Label2"] = $quantityLabels[1];
2296  }
2297  break;
2298  case "no qty2":
2299  $templateBooleans["showQuantity1Row"] = true;
2300 
2301  if ($quantityLabels == null) {
2302  $templateBooleans["quantity1Label1"] = "Quantity 1:";
2303  } else {
2304  $templateBooleans["quantity1Label1"] = $quantityLabels[0];
2305  $templateBooleans["quantity1Label2"] = $quantityLabels[1];
2306  }
2307  break;
2308  case "range":
2309  $templateBooleans["showQuantityRangeRow"] = true;
2310 
2311  if ($quantityLabels == null) {
2312  $templateBooleans["quantityRangeLabel1"] = "Range:";
2313  } else {
2314  $templateBooleans["quantityRangeLabel1"] = $quantityLabels[0];
2315  }
2316  break;
2317  case "left":
2318  $templateBooleans["showQuantity1Row"] = true;
2319  $templateBooleans["showQuantity2Row"] = true;
2320 
2321  if ($quantityLabels == null) {
2322  $templateBooleans["quantity1Label1"] = "Quantity 1:";
2323  $templateBooleans["quantity2Label1"] = "Quantity 2:";
2324  } else {
2325  $templateBooleans["quantity1Label1"] = $quantityLabels[0];
2326  $templateBooleans["quantity2Label1"] = $quantityLabels[1];
2327  }
2328  break;
2329  case "left and right":
2330  $templateBooleans["showQuantity1Row"] = true;
2331  $templateBooleans["showQuantity2Row"] = true;
2332 
2333  if ($quantityLabels == null) {
2334  $templateBooleans["quantity1Label1"] = "Quantity 1:";
2335  $templateBooleans["quantity2Label1"] = "Quantity 2:";
2336  } else {
2337  $templateBooleans["quantity1Label1"] = $quantityLabels[0];
2338  $templateBooleans["quantity1Label2"] = $quantityLabels[1];
2339  $templateBooleans["quantity2Label1"] = $quantityLabels[2];
2340  $templateBooleans["quantity2Label2"] = $quantityLabels[3];
2341  }
2342  break;
2343  case null:
2344  default:
2345  $templateBooleans["showQuantity1Row"] = true;
2346  $templateBooleans["showQuantity2Row"] = true;
2347 
2348  $templateBooleans["quantity1Label1"] = "Quantity 1:";
2349  $templateBooleans["quantity2Label1"] = "Quantity 2:";
2350  break;
2351  }
2352 
2353 
2354  }
2355 
2356  $templateBooleans["showFixedRow"] = false;
2357  $templateBooleans["showVariableRow"] = false;
2358  $templateBooleans["fixedLabel"] = "";
2359  $templateBooleans["variableLabel"] = "";
2360 
2361  switch($fixedVariableLabelType) {
2362 
2363  case "regular":
2364  case "left":
2365  $templateBooleans["showFixedRow"] = true;
2366  $templateBooleans["fixedLabel"] = $fixedVariableLabels == null ? "Fixed:" : $fixedVariableLabels[0];
2367 
2368  if ($setupLabel === "recurring") {
2369  $templateBooleans["showVariableRow"] = true;
2370  $templateBooleans["variableLabel"] = $fixedVariableLabels == null ? "Variable:" : $fixedVariableLabels[1];
2371  }
2372  break;
2373  case "none":
2374  break;
2375  case "no fixed":
2376  if ($setupLabel === "recurring") {
2377  $templateBooleans["showVariableRow"] = true;
2378  $templateBooleans["variableLabel"] = $fixedVariableLabels == null ? "Variable:" : $fixedVariableLabels[0];
2379  }
2380  break;
2381  case "no variable":
2382  $templateBooleans["showFixedRow"] = true;
2383  $templateBooleans["fixedLabel"] = $fixedVariableLabels == null ? "Fixed:" : $fixedVariableLabels[0];
2384  break;
2385  case null:
2386  default:
2387  $templateBooleans["showFixedRow"] = true;
2388  $templateBooleans["fixedLabel"] = "Fixed:";
2389 
2390  if ($setupLabel === "recurring") {
2391  $templateBooleans["showVariableRow"] = true;
2392  $templateBooleans["variableLabel"] = "Variable:";
2393  }
2394  break;
2395  }
2396 
2397  $templateBooleans["showFrequencyDiv"] = $setupLabel === "recurring";
2398  $templateBooleans["showOverrideRow"] = true;
2399 
2400  $returnArray = array("status" => "000", "error" => "", "templateBooleans" => $templateBooleans);
2401  } catch (exception $e) {
2402  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
2403  }
2404  return $returnArray;
2405 }
2406 
2407 /**
2408  * function ReadSalesOrderDetailInit($dbh, $sysenv, $Hu)
2409  * Gets all the data for static dropdownlists on the sales order detail page.
2410  *
2411  * @param $dbh -- the database connection.
2412  * @param $sysenv -- the system environment variables.
2413  * @param $Hu -- the logged in user.
2414  *
2415  * @return $status -- "000" if successful, nonzero otherwise.
2416  * @return $error -- "" if successful, nonempty otherwise.
2417  * @return $cu -- the Credit Union code. (Displays at the top of the page.)
2418  * @return $cuName -- the Credit Union name. (Displays at the top of the page.)
2419  * @return $operation -- "read"
2420  * @return $defaultProductOptions -- the starting bind options for the product.
2421  * Whenever, the add product dialog is opened or the product is changed,
2422  * this gets reset to this value.
2423  *
2424  * Product DDLs
2425  * ----------------------------
2426  * @return $ddls.unattachedJobs -- a full list of unattached jobs for the credit union.
2427  * This is filtered to the product id in the add product dialog.
2428  * @return $ddls.productItems -- a list of products.
2429  * This drives the values of the other dropdownlists.
2430  * @return $ddls.productWorkflowOptions -- This is the full list of workflow options.
2431  * @return $ddls.productOptions -- This is the product configurations available.
2432  *
2433  * Line Item DDLs
2434  * ----------------------------
2435  * @return $ddls.billingTemplateItems -- this is a list of default line item values.
2436  * They are in the same format as "ReadSalesOrderLineItem."
2437  * @return $ddls.setupBillingOptions -- the options for the bill half dropdownlist.
2438  * @return $ddls.monthDDL -- the options for the billsOn1 and billsOn2 dropdownlists.
2439  * @return $ddls.frequencyDDL -- the options for the frequency dropdownlist.
2440  * @return $ddls.startMonthOptions -- the options for the start month/end month dropdownlists.
2441  */
2442 function ReadSalesOrderDetailInit($dbh, $sysenv, $Hu) {
2443  try {
2444  extract($sysenv["BILL"]);
2445  if (!isset($salesOrderId) || trim($salesOrderId) == "") {
2446  throw new exception ("Contract id is required.", 1);
2447  }
2448  $salesOrderId = intval($salesOrderId);
2449 
2450  // Values for top of page
2451  // ----------------------------------
2452 
2453  $sql = "select trim(so.cu_id) \"cu\", trim(i.name) \"cuName\"
2454  from cubillsalesorder so
2455  inner join cuinfo i on trim(lower(so.cu_id)) = trim(lower(i.user_name))
2456  where so.id = " . intval($salesOrderId) . "
2457  order by so.cu_id";
2458  $sth = db_query($sql, $dbh);
2459  if (!$sth) {
2460  throw new exception ("Select query failed.", 2);
2461  }
2462  $cu = db_num_rows($sth) > 0 ? trim(db_fetch_row($sth, 0)[0]) : "";
2463  $cuName = db_num_rows($sth) > 0 ? trim(db_fetch_row($sth, 0)[1]) : "";
2464 
2465  // Values for product add
2466  // ------------------------------------
2467  $productAddDDLs = array();
2468 
2469  // Note: if there no product in the cuprodlist table, then it is using the first product name from the sales item table (in Alphabetical order)
2470  $sql = "select trim(si.prod_id) \"value\", trim(coalesce(p.home_cu_desc, si.billing_system_id)) \"text\"
2471  from (
2472  select trim(isi.billing_system_id), trim(isi.prod_id), row_number() over(partition by isi.prod_id order by isi.billing_system_id)
2473  from cubillsalesitem isi where isi.deleted_date is null
2474  ) si (billing_system_id, prod_id, number)
2475  left join cuprodlist p on trim(si.prod_id) = trim(p.home_cu_code)
2476  where si.number = 1
2477  order by 2";
2478  $sth = db_query($sql, $dbh);
2479  if (!$sth) {
2480  throw new exception ("Select query failed.", 3);
2481  }
2482 
2483  $unusedQueryResult = db_fetch_all($sth);
2484  $unusedQueryResult = $unusedQueryResult === false ? array() : $unusedQueryResult;
2485 
2486  $addProductOptions = GetAutomaticProductAddConfigOptions();
2487  $productsUsingDNID = GetUseDNIDProduct();
2488  $automaticProductAdds = GetAutomaticProductAdds();
2489  $setupConfig = GetSetupRecurring();
2490  $workflowDDL = GetWorkflowOptions();
2491 
2492  $sql = "select trim(t.issue) \"text\", t.track_id \"value\", trim(i.gen_for_product) \"product\"
2493  from cutrack t
2494  left join cuissues i on t.issue_id = i.issue_id
2495  where t.billing_feature_id = 0 and lower(t.user_name) = '" . prep_save(strtolower($cu), 12) . "'
2496  order by i.gen_for_product, t.issue, t.issue_id, t.track_id";
2497  $sth = db_query($sql, $dbh);
2498  if (!$sth) {
2499  throw new exception ("Select query failed.", 6);
2500  }
2501  $unattachedJobs = db_fetch_all($sth);
2502  $unattachedJobMap = array();
2503 
2504  if ($unattachedJobs !== false) {
2505  $currentProduct = "";
2506  foreach($unattachedJobs as $record) {
2507  if ($record["product"] != $currentProduct) {
2508  $currentProduct = $record["product"];
2509  $unattachedJobMap[$currentProduct] = array();
2510  }
2511  $unattachedJobMap[$currentProduct][] = array("text" => $record["text"], "value" => $record["value"]);
2512  }
2513  } else {
2514  $unattachedJobs = array();
2515  }
2516 
2517  $productAddDDLs["unattachedJobs"] = $unattachedJobs;
2518 
2519  $sql = "select trim(sod.prod_id) \"product\"
2520  from cubillsalesorderdetail sod
2521  where sales_order_id = " . intval($salesOrderId);
2522  $sth = db_query($sql, $dbh);
2523  if (!$sth) {
2524  throw new exception ("Select query failed.", 8);
2525  }
2526 
2527  $products = db_fetch_all($sth);
2528  $productMap = array();
2529  if ($products !== false) {
2530  foreach($products as $productRow) {
2531  $productMap[$productRow["product"]] = true;
2532  }
2533  }
2534 
2535  $productOptions = array();
2536  $productWorkflowOptions = array();
2537 
2538  foreach($unusedQueryResult as $record) {
2539  $optionArray = array();
2540  $product = trim($record["value"]);
2541  $options = HCU_array_key_value($product, $addProductOptions);
2542  $productHasSetup = false; // If product has configurations, check it there.
2543  $record["useDNID"] = HCU_array_key_value($product, $productsUsingDNID);
2544  $record["productExists"] = HCU_array_key_value($product, $productMap);
2545  $record["hasOptions"] = $options !== false;
2546 
2547  if ($options !== false) {
2548  foreach($options as $key => $value) {
2549  $configHasSetup = false;
2550  $itemArray = HCU_array_key_value($key, $automaticProductAdds);
2551  if ($itemArray !== false) {
2552  foreach($itemArray as $item) {
2553  $setupLabel = HCU_array_key_value($item["billingId"], $setupConfig);
2554  if ($setupLabel === "setup") {
2555  $configHasSetup = true;
2556  break;
2557  }
2558  }
2559  }
2560  $productOptions[] = array("text" => $value, "value" => $key, "hasSetup" => $configHasSetup, "product" => $product);
2561  }
2562  } else {
2563  $itemArray = HCU_array_key_value($product, $automaticProductAdds);
2564  if ($itemArray !== false) {
2565  foreach($itemArray as $item) {
2566  $setupLabel = HCU_array_key_value($item["billingId"], $setupConfig);
2567  if ($setupLabel === "setup") {
2568  $productHasSetup = true;
2569  break;
2570  }
2571  }
2572  }
2573  }
2574 
2575  $record["hasUnattachedJobs"] = HCU_array_key_exists($record["value"], $unattachedJobMap);
2576 
2577  foreach($workflowDDL as $workflowRecord) {
2578 
2579  if ($record["hasUnattachedJobs"] || $workflowRecord["value"] !== "associate") {
2580  $productWorkflowOptions[] = array("value" => $workflowRecord["value"], "text" => $workflowRecord["text"], "product" => $product);
2581  }
2582  }
2583 
2584  $record["hasSetup"] = $productHasSetup;
2585  $productArray[] = $record;
2586  }
2587 
2588  $productAddDDLs["productItems"] = $productArray;
2589  $productAddDDLs["productWorkflowOptions"] = $productWorkflowOptions;
2590  $productAddDDLs["productOptions"] = $productOptions;
2591 
2592  $defaultProductOptions = GetDefaultProductOptions($productAddDDLs);
2593 
2594  // Values for line items
2595  // ------------------------------------
2596  $lineItemDDLs = array();
2597 
2598  $sql = "select trim(si.display_name) \"salesItemText\", si.id \"salesItemValue\", trim(si.prod_id) \"productId\",
2599  si.billing_template \"template\", trim(si.billing_system_id) \"billingId\"
2600  from cubillsalesitem si
2601  where si.deleted_date is null and si.billing_template is not null and trim(from si.billing_template) <> ''
2602  order by si.display_name";
2603  $sth = db_query($sql, $dbh);
2604  if (!$sth) {
2605  throw new exception ("Select query failed.", 5);
2606  }
2607  $templateItems = db_fetch_all($sth);
2608  $templateItems = $templateItems === false ? array() : $templateItems;
2609 
2610  $expandedResults = array();
2611  foreach($templateItems as $rawRecord) {
2612  $lineItemData = array("featureDetailId" => 0, "billingStatus" => 1);
2613 
2614  $template = HCU_JsonDecode($rawRecord["template"]);
2615  unset($record["template"]);
2616  // Ignore description here. Use the description on the sales_item table.
2617  $lineItemData["fixed"] = floatval($template["fixed"]);
2618  $lineItemData["variable"] = floatval($template["variable"]);
2619  $lineItemData["qty1"] = intval($template["qty1"]);
2620  $lineItemData["qty2"] = intval($template["qty2"]);
2621  // Positive infinity is marked as null in the application (with a placeholder) and -1 in the database.
2622  $lineItemData["qty2"] = $lineItemData["qty2"] == -1 ? null : $lineItemData["qty2"];
2623  $lineItemData["aDNID"] = HCU_array_key_exists("aDNID", $template) ? $template["aDNID"] : "";
2624  $lineItemData["overrideDescription"] = trim($template["descr"]);
2625  $lineItemData["actualDescription"] = $rawRecord["salesItemText"];
2626  $lineItemData["partiallyBilledAmount"] = 0;
2627  $lineItemData["partiallyBilledDate"] = null;
2628 
2629  $billingId = $rawRecord["billingId"];
2630  $salesItemId = $rawRecord["salesItemValue"];
2631 
2632  $results = CompileLineItemTemplateBooleans($billingId, $salesItemId, false);
2633  if ($results["status"] !== "000") {
2634  throw new exception ("Configuration failed.", 7);
2635  }
2636  $configuration = $results["templateBooleans"];
2637 
2638  $bindOptions = GetDefaultLineItemBindOptions();
2639 
2640  if ($configuration["showQuantityRangeRow"]) {
2641  $bindOptions["qtyRangeVisible"] = true; // Needed to save infinity correctly.
2642  }
2643 
2644  $workflowDDL = array();
2645  $workflowUnattachedJobs = array();
2646 
2647  if (HCU_array_key_exists($billingId, $setupConfig)) {
2648  $setupLabel = $setupConfig[$billingId];
2649  } else {
2650  $setupLabel = null;
2651  }
2652 
2653  if ($setupLabel === "setup") {
2654  $workflowDDL = GetWorkflowOptions();
2655 
2656  $workflowUnattachedJobs = HCU_array_key_value($rawRecord["productId"], $unattachedJobMap);
2657  $workflowUnattachedJobs = $workflowUnattachedJobs === false ? array() : $workflowUnattachedJobs;
2658  if (count($workflowUnattachedJobs) == 0) {
2659  $newWorkflowDDL = array();
2660  foreach($workflowDDL as $record) {
2661 
2662  if ($record["value"] !== "associate") {
2663  $newWorkflowDDL[] = $record;
2664  }
2665  }
2666 
2667  $workflowDDL = $newWorkflowDDL;
2668  } else {
2669  $bindOptions["unattachedJobValue"] = intval($workflowUnattachedJobs[0]["value"]);
2670  }
2671  }
2672 
2673  $ddls = array("workflows" => $workflowDDL, "workflowUnattachedJobs" => $workflowUnattachedJobs);
2674 
2675  $record = array("salesItemText" => $rawRecord["salesItemText"], "salesItemValue" => $rawRecord["salesItemValue"],
2676  "configuration" => $configuration, "lineItemData" => $lineItemData, "bindOptions" => $bindOptions,
2677  "productId" => $rawRecord["productId"], "ddls" => $ddls);
2678 
2679  $expandedResults[] = $record;
2680  }
2681 
2682  $lineItemDDLs["billingTemplateItems"] = $expandedResults;
2683  $lineItemDDLs["setupBillingOptions"] = GetSetupBillingOptions();
2684 
2685  // Frequency
2686  $lineItemDDLs["monthDDL"] = GetMonthOptions();
2687  $lineItemDDLs["frequencyDDL"] = GetFrequencyOptions();
2688 
2689  $monthOptions = array();
2690  $timezone = new DateTimeZone("America/Denver");
2691  $monthToAdd = new DateTime("now", $timezone);
2692  $year = intval($monthToAdd->format("Y"));
2693  $month = intval($monthToAdd->format("m"));
2694  $monthToAdd = DateTime::createFromFormat("Y-m-d|", "$year-$month-01");
2695  $monthToAdd->modify("-3 months");
2696 
2697  for($i = 0; $i != 9; $i++) {
2698  $text = $monthToAdd->format("m/Y");
2699  $value = $monthToAdd->format("Y-m-d");
2700  $monthOptions[] = array("text" => $text, "value" => $value);
2701  $monthToAdd->modify("+1 month");
2702  }
2703 
2704  $lineItemDDLs["startMonthOptions"] = $monthOptions;
2705 
2706 
2707  $ddls = $productAddDDLs + $lineItemDDLs;
2708 
2709  $returnArray = array("status" => "000", "error" => "", "operation" => "read", "cu" => $cu, "cuName" => $cuName, "ddls" => $ddls,
2710  "defaultProductOptions" => $defaultProductOptions);
2711  } catch (exception $e) {
2712  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
2713  }
2714  return $returnArray;
2715 }
2716 
2717 /**
2718  * function ReadSalesOrderDetailWarnings($dbh, $sysenv, $Hu)
2719  * Gets the warnings for the sales order detail page.
2720  *
2721  * @param $dbh -- the database connection.
2722  * @param $sysenv -- the system environment variables.
2723  * @param $Hu -- the logged in user.
2724  *
2725  * @return $status -- "000" if successful, nonzero otherwise.
2726  * @return $error -- "" if successful, nonempty otherwise.
2727  * @return $warnings -- the warnings to show.
2728  */
2729 function ReadSalesOrderDetailWarnings($dbh, $sysenv, $Hu) {
2730  try {
2731  extract($sysenv["BILL"]);
2732  if (!isset($salesOrderId) || trim($salesOrderId) == "") {
2733  throw new exception ("Contract id is required.", 1);
2734  }
2735  $salesOrderId = intval($salesOrderId);
2736 
2737  $results = CheckForOverlapGaps($dbh, $salesOrderId);
2738  if ($results["status"] !== "000") {
2739  throw new exception ($results["error"], 2);
2740  }
2741  $rawWarnings = $results["warnings"];
2742  $warnings = array();
2743 
2744  foreach($rawWarnings as $productId => $newWarnings) {
2745  $warnings = array_merge($warnings, $newWarnings);
2746  }
2747 
2748  $returnArray = array("status" => "000", "error" => "", "warnings" => $warnings);
2749 
2750  } catch (exception $e) {
2751  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
2752  }
2753  return $returnArray;
2754 }
2755 
2756 /**
2757  * function ValidateProductSelected ($product, $validProductIds)
2758  * @param $product -- the product record to verify.
2759  * @param $validProductIds -- a list of valid product ids in the system.
2760  *
2761  * @return $status -- "000" if successful, nonzero otherwise.
2762  * @return $error -- "" if successful, nonempty otherwise.
2763  * @return $productAdds -- a list of line items to create for product.
2764  * @return $productId -- the product id extracted from $product.
2765  */
2766 function ValidateProductSelected ($product, $validProductIds) {
2767  try {
2768  $autoAdds = GetAutomaticProductAdds();
2769  $autoAddOptions = GetAutomaticProductAddConfigOptions();
2770  $setupConfig = GetSetupRecurring();
2771  $useDNID = GetUseDNID();
2772  $rawWorkflowOptions = GetWorkflowOptions();
2773 
2774  $rawSetupBillingOptions = GetSetupBillingOptions();
2775  $setupBillingOptions = array();
2776 
2777  foreach($rawSetupBillingOptions as $record) {
2778  $setupBillingOptions[] = $record["value"];
2779  }
2780 
2781  foreach($rawWorkflowOptions as $record) {
2782  $workflowOptions[] = $record["value"];
2783  }
2784  $workflowOptions[] = "none"; // Ensure that this option is valid as well.
2785 
2786  $productId = HCU_array_key_value("productId", $product);
2787  if ($productId === false) {
2788  throw new exception ("Product id is not found.", 1);
2789  }
2790  if (!in_array($productId, $validProductIds)) {
2791  throw new exception ("Product id is not known.", 2);
2792  }
2793  $productOption = HCU_array_key_value("productOption", $product);
2794  if ($productOption === false) {
2795  throw new exception ("Product option is not found.", 3);
2796  }
2797 
2798  $productOptions = HCU_array_key_value($productId, $autoAddOptions);
2799  $productAdds = array();
2800  if ($productOptions !== false) {
2801  $productAdds = HCU_array_key_value($productOption, $autoAdds);
2802  if ($productAdds == false) {
2803  throw new exception ("Product add array is not found.", 4);
2804  }
2805  } else {
2806  $productAdds = HCU_array_key_value($productId, $autoAdds);
2807  if ($productAdds == false) {
2808  throw new exception ("Product add array is not found.", 5);
2809  }
2810  }
2811 
2812  foreach($productAdds as $i => $addItem) {
2813  $billingId = $addItem["billingId"];
2814  if (HCU_array_key_value($billingId, $setupConfig) === "setup") {
2815  $productAdds[$i]["isSetup"] = true;
2816 
2817  $partiallyBilledDate = HCU_array_key_value("partiallyBilledDate", $product);
2818  if ($partiallyBilledDate === false) {
2819  throw new exception ("partiallyBilledDate is required.", 7);
2820  }
2821 
2822  $billHalfValue = HCU_array_key_value("billHalfValue", $product);
2823  if ($billHalfValue === false) {
2824  throw new exception ("billHalfValue is required.", 8);
2825  }
2826 
2827  $workflowValue = HCU_array_key_value("workflowValue", $product);
2828  if ($workflowValue === false) {
2829  throw new exception ("WorkflowValue is required.", 9);
2830  }
2831 
2832  $unattachedJobValue = HCU_array_key_value("unattachedJobValue", $product);
2833  if ($unattachedJobValue === false) {
2834  throw new exception ("UnattachedJobValue is required.", 10);
2835  }
2836 
2837  if (!in_array($billHalfValue, $setupBillingOptions)) {
2838  throw new exception ("BillHalfValue is invalid.", 11);
2839  }
2840 
2841  if (!in_array($workflowValue, $workflowOptions)) {
2842  throw new exception ("WorkflowValue is invalid.", 12);
2843  }
2844 
2845  $productAdds[$i]["billHalfValue"] = $billHalfValue;
2846  $productAdds[$i]["workflowValue"] = $workflowValue;
2847  $productAdds[$i]["unattachedJobValue"] = $unattachedJobValue;
2848  $productAdds[$i]["partiallyBilledDate"] = $partiallyBilledDate;
2849  } else {
2850  $productAdds[$i]["isSetup"] = false;
2851  }
2852 
2853  if (HCU_array_key_value($billingId, $useDNID)) {
2854  $productAdds[$i]["useDNID"] = false;
2855 
2856  $DNID = HCU_array_key_value("aDNID", $product);
2857  if ($DNID === false) {
2858  throw new exception ("DNID is not found.", 6);
2859  }
2860  $productAdds[$i]["DNID"] = $DNID;
2861  } else {
2862  $productAdds[$i]["useDNID"] = false;
2863  }
2864  }
2865 
2866  $returnArray = array("status" => "000", "error" => "", "productAdds" => $productAdds, "productId" => $productId);
2867  } catch (exception $e) {
2868 
2869  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
2870  }
2871  return $returnArray;
2872 }
2873 
2874 /**
2875  * function CreateSalesOrderDetails ($dbh, $sysenv, $staffId)
2876  * This function creates sales order details with its line items.
2877  *
2878  * @param $dbh -- the database connection.
2879  * @param $sysenv -- the system environment variables.
2880  * @param $Hu -- the logged in user.
2881  *
2882  * @return $status -- "000" if successful, nonzero otherwise.
2883  * @return $error -- "" if successful, nonempty otherwise.
2884  * @return $type -- "modify."
2885  * @return $orderDetailIds -- list of order detail ids created.
2886  * This is used to auto-open these details in the pending grid.
2887  * @return $workflowStatuses -- list of product names and created boolean
2888  * for products that should have created a workflow.
2889  */
2890 function CreateSalesOrderDetails ($dbh, $sysenv, $staffId) {
2891  try {
2892  $taskEmails = array();
2893  $issueEmails = array();
2894  $workflowStatuses = array();
2895  $orderDetailIds = array();
2896 
2897  extract($sysenv["BILL"]);
2898 
2899  $salesOrderId = isset($salesOrderId) ? intval($salesOrderId) : 0;
2900  $productsSelected = isset($productsSelected) ? trim($productsSelected) : "";
2901 
2902  if ($salesOrderId == 0) {
2903  throw new exception("Sales order id is required.", 1);
2904  }
2905  if ($productsSelected == "") {
2906  throw new exception("Products selected is required.", 2);
2907  }
2908 
2909  // Here, get values from the database that are needed here for labels.
2910  $sql = "select trim(so.cu_id), trim(i.name) \"cuName\"
2911  from cubillsalesorder so
2912  left join cuinfo i on lower(so.cu_id) = lower(i.user_name)
2913  where so.id = " . intval($salesOrderId);
2914  $sth = db_query($sql, $dbh);
2915  if (!$sth || db_num_rows($sth) <= 0) {
2916  throw new exception ("Select query failed.", 3);
2917  }
2918  list($cu, $cuName) = db_fetch_row($sth, 0);
2919 
2920  $sql = "select distinct trim(prod_id) \"productId\"
2921  from cubillsalesitem";
2922  $sth = db_query($sql, $dbh);
2923  if (!$sth) {
2924  throw new exception ("Select query failed.", 4);
2925  }
2926  $validProductIds = array();
2927  $results = db_fetch_all($sth);
2928  $results = $results === false ? array() : $results;
2929  foreach($results as $record) {
2930  $validProductIds[] = $record["productId"];
2931  }
2932 
2933  $products = HCU_JsonDecode($productsSelected);
2934  if (!is_array($products)) {
2935  throw new exception ("Products are encoded correctly.", 5);
2936  }
2937 
2938  $validatedProducts = array();
2939  foreach($products as $product) {
2940  $results = ValidateProductSelected ($product, $validProductIds);
2941  if ($results["status"] !== "000") {
2942  throw new exception ("Product is not valid.", 6);
2943  }
2944 
2945  $validatedProducts[] = array("productId" => $results["productId"], "productAdds" => $results["productAdds"]);
2946  }
2947 
2948  if (!db_work( $dbh, HOMECU_WORK_BEGIN )) {
2949  throw new exception("begin query failed.", 7);
2950  }
2951 
2952  foreach($validatedProducts as $validatedProductsRecord) {
2953  $productId = $validatedProductsRecord["productId"];
2954  $productAdds = $validatedProductsRecord["productAdds"];
2955 
2956  $sql = "select trim(home_cu_desc)
2957  from cuprodlist
2958  where home_cu_code = '" . prep_save($productId, 10) . "'";
2959  $sth = db_query($sql, $dbh);
2960  if (!$sth || db_num_rows($sth) <= 0) {
2961  throw new exception ("Select query failed.", 23);
2962  }
2963  list($prodText) = db_fetch_row($sth, 0);
2964 
2965  $sql = "select sod.id from cubillsalesorderdetail sod
2966  where sod.prod_id = '" . prep_save($productId, 10) . "' and sod.sales_order_id = " . intval($salesOrderId);
2967  $sth = db_query($sql, $dbh);
2968  if (!$sth) {
2969  throw new exception("id query failed.", 8);
2970  }
2971  $orderDetailId = db_num_rows($sth) >= 0 ? intval(db_fetch_row($sth, 0)[0]) : 0;
2972 
2973  if ($orderDetailId === 0) {
2974 
2975  $sql = "insert into cubillsalesorderdetail (sales_order_id, prod_id, last_modified_by, last_modified_date)
2976  values (" . intval($salesOrderId) . ", '" . prep_save($productId, 10) . "', '" . prep_save($staffId, 12) . "', now())
2977  returning id";
2978  $sth = db_query($sql, $dbh);
2979  if (!$sth || db_num_rows($sth) <= 0) {
2980  throw new exception ("Insert query failed.", 9);
2981  }
2982  $orderDetailId = intval(db_fetch_row($sth, 0)[0]);
2983  } else {
2984  $sql = "update cubillsalesorderdetail
2985  set last_modified_by = '" . prep_save($staffId, 12) . "', last_modified_date = now()
2986  where id = " . intval($orderDetailId);
2987  $sth = db_query($sql, $dbh);
2988  if (!$sth) {
2989  throw new exception ("Update query failed.", 10);
2990  }
2991  }
2992 
2993  $orderDetailIds[] = $orderDetailId;
2994 
2995  $nextMonth = new DateTime();
2996  $m = $nextMonth->format('m');
2997  $Y = $nextMonth->format('Y');
2998  $nextMonth->setDate($Y, $m + 1, 1);
2999  $nextMonth = $nextMonth->format("Y-m-d");
3000 
3001  foreach($productAdds as $productAddRecord) {
3002  $isSetup = $productAddRecord["isSetup"];
3003 
3004  $salesItemId = intval($productAddRecord["salesItemId"]);
3005 
3006  if ($isSetup && $productAddRecord["billHalfValue"] === "nosetup") {
3007  continue; // There is a setup record but do not create it.
3008  }
3009 
3010  $fieldsToSave = array();
3011 
3012  $fieldsToSave["sales_order_detail_id"] = intval($orderDetailId);
3013  $fieldsToSave["sales_item_id"] = intval($salesItemId);
3014  $fieldsToSave["start_date"] = "'" . prep_save($nextMonth) . "'";
3015  $billingStatus = 1;
3016 
3017  $sql = "select billing_template from cubillsalesitem
3018  where id = " . intval($salesItemId);
3019  $sth = db_query($sql, $dbh);
3020  if (!$sth || db_num_rows($sth) <= 0) {
3021  throw new exception ("Select query failed.", 12);
3022  }
3023  $rawBillingTemplate = db_fetch_row($sth, 0)[0];
3024  $rawBillingTemplate = HCU_JsonDecode($rawBillingTemplate);
3025  $billingTemplate = array();
3026  $descr = HCU_array_key_value("descr", $rawBillingTemplate);
3027  $descr = $descr === false ? "" : trim($descr);
3028  $fixed = HCU_array_key_value("fixed", $rawBillingTemplate);
3029  $fixed = $fixed === false ? "0.000" : strval(floatval($fixed));
3030  $variable = HCU_array_key_value("variable", $rawBillingTemplate);
3031  $variable = $variable === false ? "0.000" : strval(floatval($variable));
3032  $qty1 = HCU_array_key_value("qty1", $rawBillingTemplate);
3033  $qty1 = $qty1 === false ? "0" : strval(intval($qty1));
3034  $qty2 = HCU_array_key_value("qty2", $rawBillingTemplate);
3035  $qty2 = $qty2 === false ? "0" : strval(intval($qty2));
3036 
3037  $billingTemplate = array("descr" => $descr, "fixed" => $fixed, "variable" => $variable, "qty1" => $qty1, "qty2" => $qty2);
3038 
3039  if ($productAddRecord["useDNID"]) {
3040  $billingTemplate["aDNID"] = $productAddRecord["DNID"];
3041  }
3042 
3043  $fieldsToSave["billing_template"] = "'" . prep_save(HCU_JsonEncode($billingTemplate)) . "'";
3044 
3045  if ($isSetup) {
3046  $billHalfValue = $productAddRecord["billHalfValue"];
3047  $workflowValue = $productAddRecord["workflowValue"];
3048  $unattachedJobValue = $productAddRecord["unattachedJobValue"];
3049 
3050  $fieldsToSave["partially_billed_date"] = "null";
3051  $fieldsToSave["partially_billed_amount"] = "null";
3052 
3053  $partiallyBilledDate = $productAddRecord["partiallyBilledDate"];
3054 
3055  switch($productAddRecord["billHalfValue"]) {
3056  case "upfront":
3057  $billingStatus = 2;
3058  $fieldsToSave["start_date"] = "'" . prep_save($partiallyBilledDate) . "'";
3059  $fieldsToSave["end_date"] = "'" . prep_save($partiallyBilledDate) . "'";
3060  $fieldsToSave["workflow_completed"] = "'Y'";
3061  break;
3062  case "future":
3063  $billingStatus = 0;
3064  $fieldsToSave["bill_half_boolean"] = "'Y'";
3065  $fieldsToSave["partially_billed_date"] = "'" . prep_save($partiallyBilledDate) . "'";
3066  break;
3067  case "partial":
3068  $partiallyBilledAmount = round($fixed / 2.0, 2);
3069  $fieldsToSave["partially_billed_date"] = "'" . prep_save($partiallyBilledDate) . "'";
3070  $fieldsToSave["partially_billed_amount"] = floatval($partiallyBilledAmount);
3071  break;
3072  case "full":
3073  break;
3074  default:
3075  throw new exception ("Bill Half value is invalid.", 11); // Should get here. Should be validated above.
3076  break;
3077 
3078  }
3079 
3080  if ($workflowValue === "without") {
3081  $billingStatus = 2;
3082  $fieldsToSave["start_date"] = "'" . prep_save($partiallyBilledDate) . "'";
3083  $fieldsToSave["end_date"] = "'" . prep_save($partiallyBilledDate) . "'";
3084  }
3085 
3086  }
3087 
3088  $sql = "insert into cubillfeaturedetail (" . implode(", ", array_keys($fieldsToSave)) . ")
3089  values (" . implode(", ", array_values($fieldsToSave)) . ")
3090  returning id";
3091  $sth = db_query($sql, $dbh);
3092  if (!$sth || db_num_rows($sth) <= 0) {
3093  throw new exception ("Insert query failed.", 13);
3094  }
3095  $featureDetailId = intval(db_fetch_row($sth, 0)[0]);
3096 
3097  if ($isSetup && !in_array($workflowValue, array("none", "without"))) {
3098 
3099  switch($workflowValue) {
3100  case "create":
3101  $sql = "select 'FOUND' from cutrack where billing_feature_id = " . intval($featureDetailId);
3102  $sth = db_query($sql, $dbh);
3103  if (!$sth) {
3104  throw new exception("Cutrack found query failed.", 14);
3105  }
3106  if (db_num_rows($sth) > 0) {
3107  throw new exception("Workflow already exists.", 15);
3108  }
3109 
3110  $results = SetupWorkflow($dbh, $productId, $prodText, $featureDetailId, $cu, $cuName, $staffId);
3111  if ($results["status"] !== "000") {
3112  throw new exception ($results["error"], 16);
3113  }
3114  $taskEmails = array_merge($taskEmails, $results["data"]["taskEmails"]);
3115  $issueEmails = array_merge($issueEmails, $results["data"]["issueEmails"]);
3116  $workflowCreated = $results["data"]["workflowCreated"];
3117  $workflowStatuses[] = array("created" => $workflowCreated, "text" => $prodText);
3118 
3119  // Update billingStatus 4 (Pending workflow but no workflow created) to 1 (pending) if it doesn't need to be billed half.
3120  // Update billingStatus 4 (Pending workflow but no workflow created) to 0 (bill half) if it does need to be billed half.
3121  // Update billingStatus 5 (Pending workflow, no workflow created, and billed half) to 1 (pending.)
3122  if ($workflowCreated && in_array($billingStatus, array(4,5))) {
3123  $billingStatus = $billingStatus == 5 || $billHalfValue !== "partial" ? 1 : 0;
3124  } else if (!$workflowCreated) {
3125 
3126  // There was no workflow created but we still want to bill it upfront.
3127  if ($billHalfValue == "upfront") {
3128  $billingStatus = 2;
3129  } else {
3130  $billingStatus = 4;
3131  }
3132  }
3133  break;
3134  case "associate":
3135  if ($unattachedJobValue > 0) {
3136  throw new exception ("Job id is required.", 17);
3137  }
3138  $sql = "update cutrack set billing_feature_id = " . intval($featureDetailId) . " where track_id = " . intval($unattachedJobValue);
3139  $sth = db_query($sql, $dbh);
3140  if (!$sth) {
3141  throw new exception("Update cutrack query failed.", 18);
3142  }
3143 
3144  // Need to update the billing_ids of the triggered sales items.
3145  $sql = "update cutrackitem set billing_item_id = coalesce(fd.id, 0) from cutrackitem ti
3146  inner join cutrack t on ti.track_id = t.track_id and t.track_id = " . intval($$unattachedJobValue) . "
3147  left join cuissuesitem ii on ti.issue_item_id = ii.item_id and ii.issue_id = t.issue_id
3148  left join cubillfeaturedetail tfd on tfd.id = " . intval($featureDetailId) . "
3149  left join cubillfeaturedetail fd on fd.sales_order_detail_id = tfd.sales_order_detail_id and fd.sales_item_id = ii.sales_item_id
3150  where ti.trackitem_id = cutrackitem.trackitem_id";
3151  $sth = db_query($sql, $dbh);
3152  if (!$sth) {
3153  throw new exception("Update cutrackitem query failed.", 19);
3154  }
3155 
3156  // Update billingStatus 4 (Pending workflow but no workflow created) to 1 (pending) if it doesn't need to be billed half.
3157  // Update billingStatus 4 (Pending workflow but no workflow created) to 0 (bill half) if it does need to be billed half.
3158  // Update billingStatus 5 (Pending workflow, no workflow created, and billed half) to 1 (pending.)
3159  if (in_array($billingStatus, array(4,5))) {
3160  $billingStatus = $billingStatus == 5 || $billHalfValue !== "partial" ? 1 : 0;
3161  }
3162  break;
3163  case "without":
3164  case "none":
3165  break;
3166  default:
3167  throw new exception ("Workflow option is invalid.", 20); // Should be caught above in validation.
3168  }
3169  }
3170 
3171  $sql = "update cubillfeaturedetail set billing_status = " . intval($billingStatus) . " where id = " . intval($featureDetailId);
3172  $sth = db_query($sql, $dbh);
3173  if (!$sth) {
3174  throw new exception("Update cubillfeaturedetail query failed.", 21);
3175  }
3176  }
3177  }
3178 
3179  if (!db_work( $dbh, HOMECU_WORK_COMMIT )) {
3180  throw new exception("commit work failed.", 22);
3181  }
3182 
3183  foreach($taskEmails as $taskEmail) {
3184  CreateTaskEmail($taskEmail["to"], $taskEmail["subject"], $taskEmail["text"], $taskEmail["issue"], $taskEmail["title"], $taskEmail["description"],
3185  $cuName, $cu, $taskEmail["issueId"]);
3186  }
3187  foreach($issueEmails as $issueEmail) {
3188  CreateIssueEmail($issueEmail["to"], $issueEmail["subject"], $issueEmail["text"], $issueEmail["issueName"], "Active", $cuName, $cu,
3189  $issueEmail["issueId"]);
3190  }
3191 
3192  $returnArray = array("status" => "000", "error" => "", "type" => "modify",
3193  "orderDetailIds" => $orderDetailIds, "workflowStatuses" => $workflowStatuses);
3194  } catch (exception $e) {
3195  db_work( $dbh, HOMECU_WORK_ROLLBACK ); // Got greater problems if this fails.
3196  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "type" => "modify");
3197  }
3198  return $returnArray;
3199 }
3200 
3201 /**
3202  * function ModifyBillingFeature($dbh, $sysenv, $staffId, $isAdd, $isActive)
3203  * This modifies a line item.
3204  *
3205  * @param $dbh -- the database connection.
3206  * @param $sysenv -- the system environment variables.
3207  * @param $staffId -- the logged in user.
3208  * @param $isAdd -- if true, then add the line item.
3209  * Otherwise, update the line item.
3210  * @param $isActive -- if true, this is from the active grid.
3211  * In this case, make sure to not create workflow,
3212  * or change bill half or frequency settings.
3213  *
3214  * @return $status -- "000" if successful, nonzero otherwise.
3215  * @return $error -- "" if successful, nonempty otherwise.
3216  * @return $type -- "modify."
3217  * @return $orderDetailIds -- list of order detail ids created.
3218  * This is used to auto-open these details in the pending grid.
3219  * @return $workflowStatuses -- list of product names and created boolean
3220  * for products that should have created a workflow.
3221  */
3222 function ModifyBillingFeature($dbh, $sysenv, $staffId, $isAdd, $isActive) {
3223  try {
3224  $savepointBilling = "billingFeatureUpdate_Billing";
3225  $savepointWorkflow = "billingFeatureUpdate_Workflow";
3226  $fieldsToSave = array();
3227 
3228  extract($sysenv["BILL"]);
3229 
3230  // Id
3231  // ------------------------------
3232  $featureDetailId = isset($featureDetailId) ? intval($featureDetailId) : 0;
3233 
3234  // Brand new record.
3235  if ($isAdd) {
3236  $billingStatus = 1; // Start with "pending".
3237 
3238  $orderDetailId = isset($orderDetailId) ? trim($orderDetailId) : "";
3239  $salesItemId = isset($salesItemId) ? trim($salesItemId) : "";
3240 
3241  if ($orderDetailId == "") {
3242  throw new exception ("orderDetailId is required.", 18);
3243  }
3244  if ($salesItemId == "") {
3245  throw new exception ("salesItemId is required.", 19);
3246  }
3247 
3248  // Here, get values from the database that are needed here for labels.
3249  $sql = "select trim(si.billing_system_id), trim(si.display_name),
3250  trim(so.cu_id), trim(i.name) \"cuName\", trim(sod.prod_id), trim(p.home_cu_desc)
3251  from cubillsalesorderdetail sod
3252  inner join cubillsalesorder so on sod.sales_order_id = so.id
3253  inner join cubillsalesitem si on si.id = " . intval($salesItemId) . "
3254  left join cuinfo i on lower(so.cu_id) = lower(i.user_name)
3255  left join cuprodlist p on sod.prod_id = p.home_cu_code
3256  where sod.id = " . intval($orderDetailId);
3257  $sth = db_query($sql, $dbh);
3258 
3259  if (!$sth) {
3260  throw new exception ("Query failed.", 2);
3261  }
3262  if (db_num_rows($sth) <= 0) {
3263  throw new exception ("billingFeatureId is not found.", 17);
3264  }
3265  list($billingId, $originalDescription, $cu, $cuName, $prodId, $prodText) = db_fetch_row($sth, 0);
3266 
3267  // Update existing record.
3268  } else {
3269  if ($featureDetailId == 0) {
3270  throw new exception("billingFeatureId is required.", 1);
3271  }
3272 
3273  // Here, get values from the database that are needed here for labels.
3274  $sql = "select fd.billing_status, trim(si.billing_system_id), fd.sales_order_detail_id, trim(si.display_name),
3275  trim(so.cu_id), trim(i.name) \"cuName\", trim(sod.prod_id), trim(p.home_cu_desc)
3276  from cubillfeaturedetail fd
3277  inner join cubillsalesorderdetail sod on fd.sales_order_detail_id = sod.id
3278  inner join cubillsalesorder so on sod.sales_order_id = so.id
3279  inner join cubillsalesitem si on fd.sales_item_id = si.id
3280  left join cuinfo i on lower(so.cu_id) = lower(i.user_name)
3281  left join cuprodlist p on sod.prod_id = p.home_cu_code
3282  where fd.id = " . intval($featureDetailId);
3283  $sth = db_query($sql, $dbh);
3284 
3285  if (!$sth) {
3286  throw new exception ("Query failed.", 2);
3287  }
3288  if (db_num_rows($sth) <= 0) {
3289  throw new exception ("billingFeatureId is not found.", 17);
3290  }
3291  list($billingStatus, $billingId, $orderDetailId, $originalDescription, $cu, $cuName, $prodId, $prodText) = db_fetch_row($sth, 0);
3292  }
3293 
3294  // Template
3295  // -------------------------------
3296  $overrideDescription = isset($overrideDescription) ? trim($overrideDescription) : "";
3297  $fixed = isset($fixed) ? floatval($fixed) : 0.00;
3298  $variable = isset($variable) ? floatval($variable) : 0.00;
3299  $qty1 = isset($qty1) ? intval($qty1) : 0;
3300  $qty2 = isset($qty2) ? intval($qty2) : 0;
3301  $aDNID = isset($aDNID) ? trim($aDNID) : "";
3302 
3303  $billingTemplate = array();
3304 
3305  $billingTemplate["descr"] = $overrideDescription == "" || $overrideDescription == $originalDescription ? "" : $overrideDescription;
3306  $billingTemplate["fixed"] = $fixed;
3307  $billingTemplate["variable"] = $variable;
3308  $billingTemplate["qty1"] = $qty1;
3309  $billingTemplate["qty2"] = $qty2;
3310  $aDNID == "" ? null : $billingTemplate["aDNID"] = $aDNID;
3311 
3312  $fieldsToSave["billing_template"] = "'" . prep_save(HCU_JsonEncode($billingTemplate)) . "'";
3313 
3314  $setupConfig = GetSetupRecurring();
3315  if (HCU_array_key_exists($billingId, $setupConfig)) {
3316  $setupLabel = $setupConfig[$billingId];
3317  } else {
3318  $setupLabel = null;
3319  }
3320 
3321  if ($setupLabel === "setup" && !$isActive) {
3322  // Bill Half & workflow
3323  // ------------------------------
3324  $billHalfValue = isset($billHalfValue) ? trim($billHalfValue) : "";
3325  $workflowValue = isset($workflowValue) ? trim($workflowValue) : "";
3326  $unattachedJobValue = isset($unattachedJobValue) ? trim($unattachedJobValue) : "";
3327  $partiallyBilledAmount = isset($partiallyBilledAmount) ? trim($partiallyBilledAmount) : "";
3328  $partiallyBilledDate = isset($partiallyBilledDate) ? trim($partiallyBilledDate) : "";
3329 
3330  if ($billHalfValue == "") {
3331  throw new exception ("Bill Half option is required.", 3);
3332  }
3333  if ($workflowValue == "") {
3334  throw new exception ("Workflow option is required.", 4);
3335  }
3336 
3337  $fieldsToSave["partially_billed_date"] = "null";
3338  $fieldsToSave["partially_billed_amount"] = "null";
3339  switch($billHalfValue) {
3340  case "upfront":
3341  if ($partiallyBilledDate == "") {
3342  throw new exception ("Partially billed date is required.", 5);
3343  }
3344  $billingStatus = 2;
3345  $fieldsToSave["start_date"] = "'" . prep_save($partiallyBilledDate) . "'";
3346  $fieldsToSave["end_date"] = "'" . prep_save($partiallyBilledDate) . "'";
3347  $fieldsToSave["workflow_completed"] = "'Y'";
3348  break;
3349  case "future":
3350  if ($partiallyBilledDate == "") {
3351  throw new exception ("Partially billed date is required.", 6);
3352  }
3353  $billingStatus = 0;
3354  $fieldsToSave["bill_half_boolean"] = "'Y'";
3355  $fieldsToSave["partially_billed_date"] = "'" . prep_save($partiallyBilledDate) . "'";
3356  break;
3357  case "partial":
3358  if ($partiallyBilledDate == "") {
3359  throw new exception ("Partially billed date is required.", 7);
3360  }
3361  if ($partiallyBilledAmount == "") {
3362  throw new exception ("Partially billed amount is required.", 8);
3363  }
3364  $fieldsToSave["partially_billed_date"] = "'" . prep_save($partiallyBilledDate) . "'";
3365  $fieldsToSave["partially_billed_amount"] = floatval($partiallyBilledAmount);
3366  break;
3367  case "full":
3368  break;
3369  default:
3370  throw new exception ("Bill Half value is invalid.", 9);
3371  break;
3372 
3373  }
3374 
3375  if ($workflowValue === "without") {
3376 
3377  $billingStatus = 2;
3378  $fieldsToSave["start_date"] = "'" . prep_save($partiallyBilledDate) . "'";
3379  $fieldsToSave["end_date"] = "'" . prep_save($partiallyBilledDate) . "'";
3380  }
3381  } else if ($setupLabel === "recurring" && !$isActive) {
3382  // Frequency
3383  // ------------------------
3384  $frequencyValue = isset($frequencyValue) ? trim($frequencyValue) : "";
3385  $billsOn1Value = isset($billsOn1Value) ? trim($billsOn1Value) : "";
3386  $billsOn2Value = isset($billsOn2Value) ? trim($billsOn2Value) : "";
3387  if ($frequencyValue == "") {
3388  throw new exception ("Frequency is required.", 10);
3389  }
3390  if ($billsOn1Value == "") {
3391  throw new exception ("Bills on 1 value is required.", 11);
3392  }
3393  if ($billsOn2Value == "") {
3394  throw new exception ("Bills on 2 value is required.", 12);
3395  }
3396  if ($billsOn1Value < 0 || $billsOn1Value >= 12) {
3397  throw new exception ("Bills on 1 value is invalid.", 13);
3398  }
3399  if ($billsOn2Value < 0 || $billsOn2Value >= 12) {
3400  throw new exception ("Bills on 2 value is invalid.", 14);
3401  }
3402  if ($frequencyValue == 2 && $billsOn1Value == $billsOn2Value) {
3403  throw new exception ("Bills on values cannot be the same.", 15);
3404  }
3405  switch($frequencyValue) {
3406  case 0:
3407  $fieldsToSave["frequency"] = 0;
3408  $fieldsToSave["months_to_bill"] = -1;
3409  break;
3410  case 1:
3411  $fieldsToSave["frequency"] = 1;
3412  $fieldsToSave["months_to_bill"] = ((1 << intval($billsOn1Value)) | (1 << intval($billsOn2Value)));
3413  break;
3414  case 2:
3415  $fieldsToSave["frequency"] = 2;
3416  $fieldsToSave["months_to_bill"] = (1 << intval($billsOn1Value));
3417  break;
3418  }
3419  }
3420 
3421  if (!db_work( $dbh, HOMECU_WORK_BEGIN )) {
3422  throw new exception("begin query failed.", 16);
3423  }
3424 
3425  $sql = "savepoint $savepointBilling";
3426  $sth = db_query($sql, $dbh);
3427  if (!$sth) {
3428  throw new exception("savepoint2 query failed.", 116);
3429  }
3430 
3431  if ($isAdd) {
3432  $nextMonth = new DateTime();
3433  $m = $nextMonth->format('m');
3434  $Y = $nextMonth->format('Y');
3435  $nextMonth->setDate($Y, $m + 1, 1);
3436  $startDate = $nextMonth->format("Y-m-d");
3437 
3438  $sql = "insert into cubillfeaturedetail (sales_order_detail_id, sales_item_id, start_date)
3439  values ('" . intval($orderDetailId) . "', '" . intval($salesItemId) . "', '" . prep_save($startDate) . "')
3440  returning id";
3441  $sth = db_query($sql, $dbh);
3442  if (!$sth) {
3443  throw new exception ("Insert query failed.", 125);
3444  }
3445  $featureDetailId = db_num_rows($sth) > 0 ? intval(db_fetch_row($sth, 0)[0]) : 0;
3446  if ($featureDetailId == 0) {
3447  throw new exception ("Insert query failed.", 126);
3448  }
3449  }
3450 
3451  $setQuery = array();
3452  foreach($fieldsToSave as $field => $value) {
3453  $setQuery[] = "$field = $value"; // Already sanitized above.
3454  }
3455  $sql = "update cubillfeaturedetail set " . implode(", ", $setQuery) . " where id = " . intval($featureDetailId);
3456  $sth = db_query($sql, $dbh);
3457  if (!$sth) {
3458  throw new exception("Update cubillfeaturedetail query failed.", 117);
3459  }
3460 
3461  $sql = "update cubillsalesorderdetail set last_modified_by = '" . prep_save($staffId, 12) . "', last_modified_date = now()
3462  where id = " . intval($orderDetailId);
3463  $sth = db_query($sql, $dbh);
3464  if (!$sth) {
3465  throw new exception("Update cubillsalesorderdetail query failed.", 118);
3466  }
3467 
3468  $workflowShouldBeCreated = false;
3469  $workflowCreated = false;
3470  $workflowUsed = false;
3471  $taskEmails = array();
3472  $issueEmails = array();
3473  if ($setupLabel === "setup" && !$isActive && !in_array($workflowValue, array("none", "without"))) {
3474 
3475  switch($workflowValue) {
3476  case "create":
3477  $sql = "select 'FOUND' from cutrack where billing_feature_id = " . intval($featureDetailId);
3478  $sth = db_query($sql, $dbh);
3479  if (!$sth) {
3480  throw new exception("Cutrack found query failed.", 115);
3481  }
3482  if (db_num_rows($sth) > 0) {
3483  throw new exception("Workflow already exists.", 116);
3484  }
3485 
3486  $workflowShouldBeCreated = true;
3487 
3488  $results = SetupWorkflow($dbh, $prodId, $prodText, $featureDetailId, $cu, $cuName, $staffId);
3489  if ($results["status"] !== "000") {
3490  throw new exception ($results["error"], 117);
3491  }
3492  $taskEmails = $results["data"]["taskEmails"];
3493  $issueEmails = $results["data"]["issueEmails"];
3494  $workflowCreated = $results["data"]["workflowCreated"];
3495 
3496  // Update billingStatus 4 (Pending workflow but no workflow created) to 1 (pending) if it doesn't need to be billed half.
3497  // Update billingStatus 4 (Pending workflow but no workflow created) to 0 (bill half) if it does need to be billed half.
3498  // Update billingStatus 5 (Pending workflow, no workflow created, and billed half) to 1 (pending.)
3499  if ($workflowCreated && in_array($billingStatus, array(4,5))) {
3500  $billingStatus = $billingStatus == 5 || $billHalfValue !== "future" ? 1 : 0;
3501  } else if (!$workflowCreated) {
3502 
3503  // There was no workflow created but we still want to bill it upfront.
3504  if ($billHalfValue == "upfront") {
3505  $billingStatus = 2;
3506  } else {
3507  $billingStatus = 4;
3508  }
3509 
3510  }
3511  break;
3512  case "associate":
3513  if ($unattachedJobValue == -1) {
3514  throw new exception ("Job id is required.", 118);
3515  }
3516  $workflowUsed = true;
3517  $sql = "update cutrack set billing_feature_id = " . intval($featureDetailId) . " where track_id = " . intval($unattachedJobValue);
3518  $sth = db_query($sql, $dbh);
3519  if (!$sth) {
3520  throw new exception("Update cutrack query failed.", 119);
3521  }
3522 
3523  // Need to update the billing_ids of the triggered sales items.
3524  $sql = "update cutrackitem set billing_item_id = coalesce(fd.id, 0) from cutrackitem ti
3525  inner join cutrack t on ti.track_id = t.track_id and t.track_id = " . intval($unattachedJobValue) . "
3526  left join cuissuesitem ii on ti.issue_item_id = ii.item_id and ii.issue_id = t.issue_id
3527  left join cubillfeaturedetail tfd on tfd.id = " . intval($featureDetailId) . "
3528  left join cubillfeaturedetail fd on fd.sales_order_detail_id = tfd.sales_order_detail_id and fd.sales_item_id = ii.sales_item_id
3529  where ti.trackitem_id = cutrackitem.trackitem_id";
3530  $sth = db_query($sql, $dbh);
3531  if (!$sth) {
3532  throw new exception("Update cutrackitem query failed.", 120);
3533  }
3534 
3535  // Update billingStatus 4 (Pending workflow but no workflow created) to 1 (pending) if it doesn't need to be billed half.
3536  // Update billingStatus 4 (Pending workflow but no workflow created) to 0 (bill half) if it does need to be billed half.
3537  // Update billingStatus 5 (Pending workflow, no workflow created, and billed half) to 1 (pending.)
3538  if (in_array($billingStatus, array(4,5))) {
3539  $billingStatus = $billingStatus == 5 || $billHalfValue !== "future" ? 1 : 0;
3540  }
3541  break;
3542  case "without":
3543  case "none":
3544  break;
3545  default:
3546  throw new exception ("Workflow option is invalid.", 121);
3547  }
3548  }
3549 
3550  $sql = "update cubillfeaturedetail set billing_status = " . intval($billingStatus) . " where id = " . intval($featureDetailId);
3551  $sth = db_query($sql, $dbh);
3552  if (!$sth) {
3553  throw new exception("Update cubillfeaturedetail query failed.", 123);
3554  }
3555 
3556  if (!db_work( $dbh, HOMECU_WORK_COMMIT )) {
3557  throw new exception("commit work failed.", 124);
3558  }
3559 
3560  foreach($taskEmails as $taskEmail) {
3561  CreateTaskEmail($taskEmail["to"], $taskEmail["subject"], $taskEmail["text"], $taskEmail["issue"], $taskEmail["title"], $taskEmail["description"],
3562  $cuName, $cu, $taskEmail["issueId"]);
3563  }
3564  foreach($issueEmails as $issueEmail) {
3565  CreateIssueEmail($issueEmail["to"], $issueEmail["subject"], $issueEmail["text"], $issueEmail["issueName"], "Active", $cuName, $cu,
3566  $issueEmail["issueId"]);
3567  }
3568 
3569  $workflowStatuses = array();
3570 
3571  if ($workflowShouldBeCreated) {
3572  $workflowStatuses[] = array("created" => $workflowCreated, "text" => $prodText);
3573  }
3574 
3575  $returnArray = array("status" => "000", "error" => "", "type" => "modify", "orderDetailId" => $orderDetailId, "workflowStatuses" => $workflowStatuses);
3576 
3577  } catch (exception $e) {
3578  if ($e->getCode() >= 100) {
3579  db_work( $dbh, HOMECU_WORK_ROLLBACK ); // Got greater problems if this fails.
3580  }
3581  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "type" => "modify");
3582  }
3583  return $returnArray;
3584 }
3585 
3586 /**
3587  * function RemoveBillingFeature($dbh, $sysenv, $staffId)
3588  * This removes a line item.
3589  *
3590  * @param $dbh -- the database connection.
3591  * @param $sysenv -- the system environment variables.
3592  * @param $staffId -- the logged in user.
3593  *
3594  * @return $status -- "000" if successful, nonzero otherwise.
3595  * @return $error -- "" if successful, nonempty otherwise.
3596  * @return $type -- "delete."
3597  * @return $removeContractDetail -- If true, then also delete the sales order detail.
3598  * If true, then refresh the pending grid.
3599  * @return $orderDetailId -- The id of the sales order detail.
3600  * Used to identify and refresh the detail grid.
3601  */
3602 function RemoveBillingFeature($dbh, $sysenv, $staffId) {
3603  try {
3604  extract($sysenv["BILL"]);
3605 
3606  if (!isset($featureDetailId)) {
3607  throw new exception ("Id is required.", 7);
3608  }
3609 
3610  $sql = "select sales_order_detail_id from cubillfeaturedetail where id = " . intval($featureDetailId);
3611  $sth = db_query($sql, $dbh);
3612  if (!$sth) {
3613  throw new exception ("Select query failed.", 8);
3614  }
3615  $orderDetailId = db_num_rows($sth) > 0 ? intval(db_fetch_row($sth, 0)[0]) : 0;
3616 
3617  $sql = "select count(*) from cubillfeaturedetail fd
3618  where fd.sales_order_detail_id = " . intval($orderDetailId) . "
3619  limit 1";
3620  $sth = db_query($sql, $dbh);
3621  if (!$sth) {
3622  throw new exception ("Select query failed.", 1);
3623  }
3624  $num = db_num_rows($sth) > 0 ? intval(db_fetch_row($sth, 0)[0]) : 0;
3625 
3626  $removeContractDetail = $num <= 1;
3627 
3628  if (!db_work( $dbh, HOMECU_WORK_BEGIN )) {
3629  throw new exception("Begin query failed.", 2);
3630  }
3631 
3632  $sql = "delete from cubillfeaturedetail where id = " . intval($featureDetailId);
3633  $sth = db_query($sql, $dbh);
3634 
3635  if (!$sth) {
3636  throw new exception ("Delete query failed.", 103);
3637  }
3638 
3639  if ($removeContractDetail) {
3640  $sql = "delete from cubillsalesorderdetail
3641  where id = " . intval($orderDetailId) . " and not exists (select 'FOUND' from cubillfeaturedetail
3642  where sales_order_detail_id = " . intval($orderDetailId) . ")";
3643  $sth = db_query($sql, $dbh);
3644 
3645  if (!$sth) {
3646  throw new exception ("Delete query failed.", 104);
3647  }
3648  }
3649 
3650  $sql = "update cubillsalesorderdetail set last_modified_by = '" . prep_save($staffId, 12) . "', last_modified_date = now()
3651  where id = " . intval($orderDetailId);
3652  $sth = db_query($sql, $dbh);
3653 
3654  if (!$sth) {
3655  throw new exception ("Update query failed.", 105);
3656  }
3657 
3658  if (!db_work( $dbh, HOMECU_WORK_COMMIT )) {
3659  throw new exception("Commit work failed.", 106);
3660  }
3661 
3662  $returnArray = array("status" => "000", "error" => "", "removeContractDetail" => $removeContractDetail, "type" => "delete", "orderDetailId" => $orderDetailId);
3663  } catch (exception $e) {
3664  if ($e->getCode() >= 100) {
3665  db_work( $dbh, HOMECU_WORK_ROLLBACK ); // Got greater problems if this fails.
3666  }
3667  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "type" => "delete");
3668  }
3669  return $returnArray;
3670 }
3671 
3672 /**
3673  * function RemoveSalesOrderDetail($dbh, $sysenv, $staffId)
3674  * This removes a sales order detail.
3675  *
3676  * @param $dbh -- the database connection.
3677  * @param $sysenv -- the system environment variables.
3678  * @param $Hu -- the logged in user
3679  *
3680  * @return $status -- "000" if successful, nonzero otherwise.
3681  * @return $error -- "" if successful, nonempty otherwise.
3682  * @return $type -- "delete."
3683  */
3684 function RemoveSalesOrderDetail($dbh, $sysenv, $staffId) {
3685  try {
3686  extract($sysenv["BILL"]);
3687 
3688  if (!isset($orderDetailId)) {
3689  throw new exception ("Detail id is required.", 1);
3690  }
3691  $orderDetailId = intval($orderDetailId);
3692 
3693  $sql = "delete from cubillsalesorderdetail where id = " . intval($orderDetailId);
3694  $sth = db_query($sql, $dbh);
3695  if (!$sth) {
3696  throw new exception ("Delete query failed.", 2);
3697  }
3698 
3699  $returnArray = array("status" => "000", "error" => "", "type" => "delete");
3700  } catch (exception $e) {
3701  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "type" => "delete");
3702  }
3703  return $returnArray;
3704 }
3705 
3706 /**
3707  * function ModifyStatus($dbh, $sysenv, $staffId)
3708  * This "advances" the line item as it where.
3709  * From the pending grid, it can be started.
3710  * From the active grid, it can be completed.
3711  * From the completed grid, it can be reactivated.
3712  * Each of those subactions have a "revert" option.
3713  *
3714  * @param $dbh -- the database connection.
3715  * @param $sysenv -- the system environment variables.
3716  * @param $Hu -- the logged in user
3717  *
3718  * @return $status -- "000" if successful, nonzero otherwise.
3719  * @return $error -- "" if successful, nonempty otherwise.
3720  * @return $type -- "changeStart", "revertStatus", or "advanceStatus."
3721  * "changeStart" or "advanceStatus" -- the grid includes these ids (for "undo" statuses.)
3722  * "revertStatus" -- the grid no longer includes the ids (the ids are already part of the query.)
3723  * @return $ids -- the id(s) that were changed.
3724  * @return $orderDetailId -- the order detail id where the line item is.
3725  * (To refresh the detail grid.)
3726  * @return $isRecurringBtn -- if coming from the "Show Recurring" button.
3727  * If true, then I have to update this label.
3728  * (It doesn't change when refreshing grid.)
3729  */
3730 function ModifyStatus($dbh, $sysenv, $staffId) {
3731  try {
3732  extract($sysenv["BILL"]);
3733 
3734  if (!isset($childType)) {
3735  throw new exception ("Child Type is required.", 1);
3736  }
3737 
3738  $pendingWhere = GetWhereForPendingLineItems();
3739  $activeWhere = GetWhereForActiveLineItems();
3740  $completedWhere = GetWhereForCompletedLineItems();
3741 
3742  $ids = array();
3743  $fieldsToSave = array();
3744  $isRecurringBtn = false;
3745 
3746  switch($childType) {
3747  case "startPending":
3748  if (!isset($startDate)) {
3749  throw new exception ("Start date is required.", 2);
3750  }
3751  if (!isset($featureDetailId)) {
3752  throw new exception ("Feature detail id is required.", 3);
3753  }
3754 
3755  $sql = "select fd.id, fd.billing_status, fd.sales_order_detail_id
3756  from cubillfeaturedetail fd
3757  left join cubillsalesitem si on fd.sales_item_id = si.id
3758  where fd.id = " . intval($featureDetailId) . " and ($pendingWhere)";
3759  $sth = db_query($sql, $dbh);
3760  if (!$sth) {
3761  throw new exception ("Select query failed.", 4);
3762  }
3763  if (db_num_rows($sth) <= 0) {
3764  throw new exception ("Feature detail is not in pending detail grid.", 5);
3765  }
3766  list($featureDetailId, $billingStatus, $orderDetailId) = db_fetch_row($sth, 0);
3767 
3768  $fieldsToSave["start_date"] = "'" . prep_save($startDate) . "'";
3769  $fieldsToSave["end_date"] = "null";
3770  $fieldsToSave["billing_status"] = 2;
3771  $ids[] = intval($featureDetailId);
3772  $type = "advanceStatus";
3773 
3774  break;
3775  case "undoPending":
3776  if (!isset($featureDetailId)) {
3777  throw new exception ("Feature detail id is required.", 3);
3778  }
3779 
3780  $sql = "select fd.id, fd.billing_status, fd.sales_order_detail_id
3781  from cubillfeaturedetail fd
3782  left join cubillsalesitem si on fd.sales_item_id = si.id
3783  where fd.id = " . intval($featureDetailId);
3784  $sth = db_query($sql, $dbh);
3785  if (!$sth) {
3786  throw new exception ("Select query failed.", 9);
3787  }
3788  if (db_num_rows($sth) <= 0) {
3789  throw new exception ("No records.", 10);
3790  }
3791  list($featureDetailId, $billingStatus, $orderDetailId) = db_fetch_row($sth, 0);
3792 
3793  $fieldsToSave["billing_status"] = 1;
3794  $ids[] = intval($featureDetailId);
3795  $type = "revertStatus";
3796 
3797  break;
3798  case "startRecurring":
3799  if (!isset($startDate)) {
3800  throw new exception ("Start date is required.", 12);
3801  }
3802  if (!isset($orderDetailId)) {
3803  throw new exception ("Order detail id is required.", 13);
3804  }
3805  $sql = "select fd.id, fd.billing_status \"billingStatus\", fd.sales_order_detail_id \"orderDetailId\", trim(si.billing_system_id) \"billingId\"
3806  from cubillfeaturedetail fd
3807  left join cubillsalesitem si on fd.sales_item_id = si.id
3808  where fd.sales_order_detail_id = " . intval($orderDetailId) . " and ($pendingWhere)
3809  order by fd.id";
3810  $sth = db_query($sql, $dbh);
3811 
3812  if (!$sth) {
3813  throw new exception ("Select query failed.", 15);
3814  }
3815  $results = db_fetch_all($sth);
3816  $results = $results === false ? array() : $results;
3817  $ids = array();
3818  $setupRecurring = GetSetupRecurring();
3819  foreach($results as $record) {
3820 
3821  $setupLabel = HCU_array_key_value($record["billingId"], $setupRecurring);
3822  if ($setupLabel === "recurring") {
3823  $ids[] = intval($record["id"]);
3824 
3825  if ($record["billingStatus"] != 1) {
3826  throw new exception ("Feature detail is not in pending detail grid.", 14);
3827  }
3828  }
3829  }
3830 
3831  $fieldsToSave["start_date"] = "'" . prep_save($startDate) . "'";
3832  $fieldsToSave["end_date"] = "null";
3833  $fieldsToSave["billing_status"] = 2;
3834  $type = "advanceStatus";
3835  $isRecurringBtn = true;
3836 
3837  break;
3838  case "undoRecurring":
3839  if (!isset($orderDetailId)) {
3840  throw new exception ("Order detail id is required.", 16);
3841  }
3842  if (!isset($includeIds)) {
3843  throw new exception ("Include ids are required.", 17);
3844  }
3845 
3846  $includeIds = HCU_JsonDecode($includeIds);
3847  $includeIds = array_map("intval", $includeIds);
3848 
3849  $includePhrase = count($includeIds) > 0 ? "and fd.id in (" . implode(", ", $includeIds) . ")" : "and false";
3850 
3851  $sql = "select fd.id, fd.billing_status \"billingStatus\", fd.sales_order_detail_id \"orderDetailId\", trim(si.billing_system_id) \"billingId\"
3852  from cubillfeaturedetail fd
3853  left join cubillsalesitem si on fd.sales_item_id = si.id
3854  where fd.sales_order_detail_id = " . intval($orderDetailId) . " $includePhrase
3855  order by fd.id";
3856  $sth = db_query($sql, $dbh);
3857 
3858  if (!$sth) {
3859  throw new exception ("Select query failed.", 18);
3860  }
3861  $results = db_fetch_all($sth);
3862  $results = $results === false ? array() : $results;
3863  $ids = array();
3864  $setupRecurring = GetSetupRecurring();
3865  foreach($results as $record) {
3866 
3867  $setupLabel = HCU_array_key_value($record["billingId"], $setupRecurring);
3868  if ($setupLabel === "recurring" && $record["billingStatus"] == 2) {
3869  $ids[] = intval($record["id"]);
3870  }
3871  }
3872 
3873  $fieldsToSave["billing_status"] = 1;
3874  $type = "revertStatus";
3875  $isRecurringBtn = true;
3876 
3877  break;
3878  case "changeStart":
3879  if (!isset($startDate)) {
3880  throw new exception ("Start date is required.", 19);
3881  }
3882  if (!isset($featureDetailId)) {
3883  throw new exception ("Feature detail id is required.", 20);
3884  }
3885 
3886  $sql = "select fd.id, fd.billing_status, fd.sales_order_detail_id
3887  from cubillfeaturedetail fd
3888  left join cubillsalesitem si on fd.sales_item_id = si.id
3889  where fd.id = " . intval($featureDetailId) . " and ($pendingWhere)";
3890  $sth = db_query($sql, $dbh);
3891  if (!$sth) {
3892  throw new exception ("Select query failed.", 21);
3893  }
3894  if (db_num_rows($sth) <= 0) {
3895  throw new exception ("Feature detail is not in pending detail grid.", 22);
3896  }
3897  list($featureDetailId, $billingStatus, $orderDetailId) = db_fetch_row($sth, 0);
3898 
3899  $fieldsToSave["start_date"] = "'" . prep_save($startDate) . "'";
3900  $fieldsToSave["end_date"] = "'" . prep_save($startDate) . "'";
3901  $ids[] = intval($featureDetailId);
3902  $type = "changeStart";
3903 
3904  break;
3905  case "reactivateCompleted":
3906  if (!isset($featureDetailId)) {
3907  throw new exception ("Feature detail id is required.", 23);
3908  }
3909 
3910  $sql = "select fd.id, fd.billing_status, fd.sales_order_detail_id
3911  from cubillfeaturedetail fd
3912  left join cubillsalesitem si on fd.sales_item_id = si.id
3913  where fd.id = " . intval($featureDetailId) . " and ($completedWhere)";
3914  $sth = db_query($sql, $dbh);
3915  if (!$sth) {
3916  throw new exception ("Select query failed.", 24);
3917  }
3918  if (db_num_rows($sth) <= 0) {
3919  throw new exception ("Feature detail is not in completed detail grid.", 25);
3920  }
3921  list($featureDetailId, $billingStatus, $orderDetailId) = db_fetch_row($sth, 0);
3922 
3923  $fieldsToSave["end_date"] = "null";
3924  $ids[] = intval($featureDetailId);
3925  $type = "advanceStatus";
3926 
3927  break;
3928  case "undoCompleted":
3929  if (!isset($featureDetailId)) {
3930  throw new exception ("Feature detail id is required.", 26);
3931  }
3932 
3933  $sql = "select fd.id, fd.billing_status, fd.sales_order_detail_id, fd.start_date
3934  from cubillfeaturedetail fd
3935  left join cubillsalesitem si on fd.sales_item_id = si.id
3936  where fd.id = " . intval($featureDetailId);
3937  $sth = db_query($sql, $dbh);
3938  if (!$sth) {
3939  throw new exception ("Select query failed.", 27);
3940  }
3941  if (db_num_rows($sth) <= 0) {
3942  throw new exception ("Feature detail is not in completed detail grid.", 28);
3943  }
3944  list($featureDetailId, $billingStatus, $orderDetailId, $startDate) = db_fetch_row($sth, 0);
3945 
3946  $fieldsToSave["end_date"] = "'" . prep_save($startDate) . "'";
3947  $ids[] = intval($featureDetailId);
3948  $type = "revertStatus";
3949 
3950  break;
3951  case "completeActive":
3952  if (!isset($featureDetailId)) {
3953  throw new exception ("Feature detail id is required.", 29);
3954  }
3955  if (!isset($endDate)) {
3956  throw new exception ("End date is required.", 35);
3957  }
3958 
3959  $sql = "select fd.id, fd.billing_status, fd.sales_order_detail_id, fd.start_date
3960  from cubillfeaturedetail fd
3961  left join cubillsalesitem si on fd.sales_item_id = si.id
3962  where fd.id = " . intval($featureDetailId) . " and ($activeWhere)";
3963  $sth = db_query($sql, $dbh);
3964  if (!$sth) {
3965  throw new exception ("Select query failed.", 30);
3966  }
3967  if (db_num_rows($sth) <= 0) {
3968  throw new exception ("Feature detail is not in active detail grid.", 31);
3969  }
3970  list($featureDetailId, $billingStatus, $orderDetailId, $startDate) = db_fetch_row($sth, 0);
3971 
3972  $fieldsToSave["end_date"] = "'" . prep_save($endDate) . "'";
3973  $ids[] = intval($featureDetailId);
3974  $type = "advanceStatus";
3975 
3976  break;
3977  case "undoActive":
3978  if (!isset($featureDetailId)) {
3979  throw new exception ("Feature detail id is required.", 32);
3980  }
3981 
3982  $sql = "select fd.id, fd.billing_status, fd.sales_order_detail_id
3983  from cubillfeaturedetail fd
3984  left join cubillsalesitem si on fd.sales_item_id = si.id
3985  where fd.id = " . intval($featureDetailId);
3986  $sth = db_query($sql, $dbh);
3987  if (!$sth) {
3988  throw new exception ("Select query failed.", 33);
3989  }
3990  if (db_num_rows($sth) <= 0) {
3991  throw new exception ("Feature detail is not in completed detail grid.", 34);
3992  }
3993  list($featureDetailId, $billingStatus, $orderDetailId) = db_fetch_row($sth, 0);
3994 
3995  $fieldsToSave["end_date"] = "null";
3996  $ids[] = intval($featureDetailId);
3997  $type = "revertStatus";
3998 
3999  break;
4000  default:
4001  throw new exception ("Child type is unknown.", 6);
4002  }
4003 
4004  if (count($ids) > 0) {
4005  $setQuery = array();
4006  foreach($fieldsToSave as $field => $value) {
4007  $setQuery[] = "$field = $value"; // Already sanitized above.
4008  }
4009  $sql = "update cubillfeaturedetail set " . implode(", ", $setQuery) . " where id in (" . implode(", ", $ids) . ")";
4010  $sth = db_query($sql, $dbh);
4011  if (!$sth) {
4012  throw new exception("Update cubillfeaturedetail query failed.", 8);
4013  }
4014  }
4015 
4016  $returnArray = array("status" => "000", "error" => "", "ids" => $ids, "type" => $type, "orderDetailId" => $orderDetailId, "isRecurringBtn" => $isRecurringBtn);
4017  } catch (exception $e) {
4018  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "type" => $type);
4019  }
4020  return $returnArray;
4021 }
4022 
4023 // =================================
4024 // END PAGE 5: Sales Order Detail
4025 // =================================