Odyssey
hcuTransferScheduled.i
1 <?php
2 /**
3  * @package hcuTransferScheduled.i
4  * @author MGHandy
5  * @uses this is a global script used for creating/
6  * updating and deleting scheduled transactions. this script
7  * facilitates in retrieving the available scheduled transfer
8  * interval list as well as calculating the next triggerdate for
9  * a scheduled transfer.
10  *
11  * Original creation date: 3/17/17
12  *
13  * NOTES:
14  * 1. This file also has the core code to enter the transfer into the database and to update the database to signify the
15  * transfer has been approved. The file should be renamed to hcuTransfer.i because it isn't particular to scheduled
16  * transfers.
17  */
18 
19 /**
20  * TxnSchedRead:
21  * @uses read all repeating scheduled transfers
22  *
23  * @param $pEnv - HB_ENV values
24  * @param $pDbh - database object
25  * @param $pMC - dictionary object
26  *
27  * @return $retStatusAry - status of request and list
28  * of current scheduled transfers
29  */
30 function TxnSchedRead($pEnv, $pDbh, $pMC) {
31  $retStatusAry = Array(
32  'status' => Array('code'=>'000', 'errors' => Array()),
33  'data' => ''
34  );
35 
36  /**
37  * select all data from cu_scheduledtxn
38  */
39  $sqlColumns = "
40  id AS tx_id,
41  cu AS tx_cu,
42  feature_code AS tx_feature,
43  start_date AS tx_start,
44  end_date AS tx_end,
45  next_trigger_date AS tx_next,
46  interval_count AS tx_interval_count,
47  status AS tx_status,
48  approved_status AS tx_approved,
49  repeating_parameters AS tx_parameters,
50  txn_data AS tx_data";
51  $sqlSelect = "
52  SELECT $sqlColumns
53  FROM cu_scheduledtxn
54  WHERE cu = '{$pEnv['Cu']}'
55  AND user_id = '{$pEnv['Uid']}'";
56  $sqlSelectRs = db_query($sqlSelect, $pDbh);
57  if (!$sqlSelectRs) {
58  $retStatusAry['status']['code'] = "999";
59  $retStatusAry['status']['severity'] = "ERROR";
60  $retStatusAry['status']['errors'][] = $pMC->msg("Transfers Unavailable", HCU_DISPLAY_AS_RAW);
61  } else {
62  // ** must iterate scheduled transactions to get the account names
63  // ** create new object array to include from/to account descriptions
64  $sqlResult = db_fetch_all($sqlSelectRs);
65  $sqlData = array();
66  if ($sqlResult != null) {
67  foreach ($sqlResult as $key => $value) {
68 
69  // decode json txn_data to get to/from/record types
70  $txData = HCU_JsonDecode($value['tx_data']);
71  $txParam = HCU_JsonDecode($value['tx_parameters']);
72  $txAmount = $txData['txn']['amount'];
73  $txMemo = $txData['txn']['memo'];
74  $txFromDesc = "";
75  $txToDesc = "";
76  $txFromSuffix = "";
77  $txToSuffix = "";
78 
79  // if external/m2m/ach then need to get partner information
80  if ( trim($value['tx_feature']) == "ACHCOL" ) {
81  $txFromDesc = _GetACHPartnerDisplay( $pEnv, $txData['txn']['from'] );
82  $txFromSuffix = "AC|{$pEnv['Uid']}|{$txData['txn']['from']}";
83 
84  $acctParts = explode( "|", $txData['txn']['to'] );
85  $certNumber = isset( $acctParts[3] ) && $acctParts[3] > 0 ? $acctParts[3] : 0;
86  $txToDesc = FindAccountDisplay( $pEnv, $pDbh, $pEnv['Cu'], $acctParts[1], $acctParts[2], $acctParts[0], $certNumber);
87  $txToSuffix = $txData['txn']['to'];
88 
89  } else if ( trim($value['tx_feature']) == "ACHPMT" ) {
90  $txToDesc = _GetACHPartnerDisplay( $pEnv, $txData['txn']['to'] );
91  $txToSuffix = "AP|{$pEnv['Uid']}|{$txData['txn']['to']}";
92 
93  $acctParts = explode( "|", $txData['txn']['from'] );
94  $certNumber = isset( $acctParts[3] ) && $acctParts[3] > 0 ? $acctParts[3] : 0;
95  $txFromDesc = FindAccountDisplay( $pEnv, $pDbh, $pEnv['Cu'], $acctParts[1], $acctParts[2], $acctParts[0], $certNumber);
96  $txFromSuffix = $txData['txn']['from'];
97  } else if ( trim($value['tx_feature']) == "TRNEXT" ) {
98  // need to figure out direction
99  if ( $txData["txn"]["type"] == "X2L" ) {
100  // external to local
101  $txFromDesc = _GetExtPartnerDisplay( $pEnv, $txData['txn']['from'] );
102  $txFromSuffix = "X|{$pEnv['Uid']}|{$txData['txn']['from']}";
103 
104  $acctParts = explode( "|", $txData['txn']['to'] );
105  $certNumber = isset( $acctParts[3] ) && $acctParts[3] > 0 ? $acctParts[3] : 0;
106  $txToDesc = FindAccountDisplay( $pEnv, $pDbh, $pEnv['Cu'], $txData['txn']['tomember'], $txData['txn']['tosuffix'], $txData['txn']['totype'], $certNumber);
107  $txToSuffix = $txData['txn']['to'];
108  } else {
109  $txToDesc = _GetExtPartnerDisplay( $pEnv, $txData['txn']['to'] );
110  $txToSuffix = "X|{$pEnv['Uid']}|{$txData['txn']['to']}";
111 
112  $acctParts = explode( "|", $txData['txn']['from'] );
113  $certNumber = isset( $acctParts[3] ) && $acctParts[3] > 0 ? $acctParts[3] : 0;
114  $txFromDesc = FindAccountDisplay( $pEnv, $pDbh, $pEnv['Cu'], $txData['txn']['frommember'], $txData['txn']['fromsuffix'], $txData['txn']['fromtype'], $certNumber);
115  $txFromSuffix = $txData['txn']['from'];
116  }
117  } else if ( trim($value['tx_feature']) == "TRNM2M" ) {
118  // only local to member allowed
119  $txToDesc = _GetExtPartnerDisplay( $pEnv, $txData['txn']['to'] );
120  $txToSuffix = "M|{$pEnv['Uid']}|{$txData['txn']['to']}";
121 
122  $acctParts = explode( "|", $txData['txn']['from'] );
123  $certNumber = isset( $acctParts[3] ) && $acctParts[3] > 0 ? $acctParts[3] : 0;
124  $txFromDesc = FindAccountDisplay( $pEnv, $pDbh, $pEnv['Cu'], $txData['txn']['frommember'], $txData['txn']['fromsuffix'], $txData['txn']['fromtype'], $certNumber);
125  $txFromSuffix = $txData['txn']['from'];
126  } else {
127  // internal to internal
128 
129  $acctPartsTo = explode( "|", $txData['txn']['to'] );
130  $acctPartsFrom = explode( "|", $txData['txn']['from'] );
131 
132  $txFromDesc = FindAccountDisplay($pEnv, $pDbh, $pEnv['Cu'], $txData['txn']['frommember'], $txData['txn']['fromsuffix'], $txData['txn']['fromtype'], isset($acctPartsFrom[3]) ? $acctPartsFrom[3] : 0);
133  $txFromSuffix = $txData['txn']['from'];
134 
135  /*
136  * Cross Account Lookup
137  * if the transactioncode is in {XA, XP} then supply differnet values for looking up the Account Description
138  */
139  if (in_array($txData['txn']['transactioncode'], array("XA", "XP"))) {
140  /*
141  * Trying this logic. I would like to skip needing to make a call to TX_List for the account information
142  * Cross Account Transfers need to be made within the same "Member" account. They can not cross over to a
143  * different member number source.
144  * Therefore, I should be able to use the 'frommember' as the member number to lookup in the useraccounts table.
145  */
146  $localToMbr = $txData['txn']['frommember'];
147  $localToSfx = $txData['txn']['tosuffix'] . '#' . $txData['txn']['tomember'];
148  $localToCert = 0; // ** Always 0 for Cross Account Lookup
149  } else {
150  $localToMbr = $txData['txn']['tomember'];
151  $localToSfx = $txData['txn']['tosuffix'];
152  $localToCert = (isset($acctPartsTo[3]) ? $acctPartsTo[3] : 0);
153  }
154 
155  $txToDesc = FindAccountDisplay($pEnv, $pDbh, $pEnv['Cu'], $localToMbr, $localToSfx, $txData['txn']['totype'], $localToCert);
156  $txToSuffix = $txData['txn']['to'];
157  }
158 
159  // determine the feature name
160  $feature = trim( $value['tx_feature'] );
161  if ( $feature == "TRN" ) {
162  $featureName = $pMC->msg("Transfer", HCU_DISPLAY_AS_RAW);
163  } else if ( $feature == "TRNEXT" ) {
164  $featureName = $pMC->msg("External Transfer", HCU_DISPLAY_AS_RAW);
165  } else if ( $feature == "TRNM2M" ) {
166  $featureName = $pMC->msg("M2M Transfer", HCU_DISPLAY_AS_RAW);
167  } else if ( $feature == "ACHPMT" ) {
168  $featureName = $pMC->msg("ACH Payment", HCU_DISPLAY_AS_RAW);
169  } else if ( $feature == "ACHCOL" ) {
170  $featureName = $pMC->msg("ACH Collection", HCU_DISPLAY_AS_RAW);
171  } else {
172  $featureName = $pMC->msg("Unknown", HCU_DISPLAY_AS_RAW);
173  }
174 
175  // return the data
176  $sqlData[] = array(
177  "txId" => intval($value['tx_id']),
178  "txFeature" => $featureName,
179  "txFromDesc" => trim($txFromDesc),
180  "txFromSuffix" => trim($txFromSuffix),
181  "txToDesc" => trim($txToDesc),
182  "txToSuffix" => trim($txToSuffix),
183  "txAmount" => floatval($txAmount),
184  "txMemo" => trim($txMemo),
185  "txStatus" => trim($value['tx_status']),
186  "txApproved" => intval($value['tx_approved']),
187  "txFrequency" => trim($txParam['interval']),
188  "txFrequencyCount" => intval($value['tx_interval_count']),
189  "txDateStart" => trim($value['tx_start']),
190  "txDateEnd" => trim($value['tx_end']),
191  "txDateNext" => trim($value['tx_next']),
192  "txContinue" => ($value['tx_end'] == null || ($value['tx_end'] == $value['tx_start'])) ?
193  "continuous" :
194  "continueuntil"
195  );
196  }
197  }
198 
199  $retStatusAry['status']['code'] = "000";
200  $retStatusAry['status']['severity'] = "SUCCESS";
201  $retStatusAry['data'] = $sqlData;
202  }
203 
204  return $retStatusAry;
205 }
206 
207 /**
208  * TxnSchedCreate:
209  * @uses create a repeating scheduled transfer
210  *
211  * @param $pEnv - HB_ENV values
212  * @param $pDbh - database object
213  * @param $pMC - dictionary object
214  * @param $pValues - transaction values
215  * @param $pFeature - feature code to test for permissions
216  *
217  * @return $retStatusAry - status of request
218  */
219 function TxnSchedCreate($pEnv, $pDbh, $pMC, $pValues) {
220  $retStatusAry = Array(
221  'status' => Array('code'=>'000', 'errors' => Array()),
222  'data' => ''
223  );
224  /**
225  * must retrieve permission for this feature
226  * when creating a scheduled record, the approval cannot
227  * be set if the user needs confirmation from another
228  * source.
229  */
230  $confirmInputs = array("feature" => $pValues['txFeature']);
231  $confirmRequired = Perm_CheckConfirmReq($pDbh, $pEnv, $confirmInputs);
232  /**
233  * REQUIRED / NOT NULLABLE
234  *
235  * cu, feature_code, user_id
236  */
237  $featureCode = $pValues['txFeature'];
238  $cu = prep_save($pEnv['Cu'], 10);
239  $userID = intval($pEnv['Uid']);
240  $name = "NULL";
241  /**
242  * NULLABLE FIELDS
243  *
244  * create_date, next_trigger_date, start_date, end_date
245  * approved_date, approved_by
246  * last_edit_date, last_edit_by
247  * interval_count, failure_count
248  * status,
249  * repeating_parameters, txn_data
250  */
251 
252  $created = "CURRENT_TIMESTAMP";
253 
254  $trigger = "NULL";
255  $trigger = TxNextInterval(
256  $pValues['txDateStart'],
257  $pValues['txFrequency'],
258  $pValues['txFrequencyCount']);
259  $trigger = $trigger ? "'" . $trigger . "'" : "NULL";
260 
261  // ** the initial trigger date when the schedule is created.
262  // * we set the start date to the first initial trigger date
263  // * not the value from txDateStart.
264 
265  if ( $pValues['txFrequency'] == "OneTime" ) {
266  $end = "'{$pValues['txDateStart']}'";
267  } else {
268  $end = $pValues['txDateEnd'] == "" ? "NULL" : "'{$pValues['txDateEnd']}'";
269  }
270 
271  // ** check permissions, set null if confirmation is needed
272  $approvedDate = $confirmRequired ? "NULL" : "CURRENT_TIMESTAMP" ;
273  $approvedBy = $confirmRequired ? "NULL" : $userID ;
274  $approvedStatus = $confirmRequired ? 0 : 10;
275 
276  // ** creation is the first edit, set to this user
277  $editedDate = $created;
278  $editedBy = $userID;
279 
280  // ** number of times the intervals have been attempted
281  $intervalCount = 0;
282  $intervalFailure = 0;
283 
284  // ** active or inactive scheduled transfer,
285  // ** initially active on create.
286  $status = "'A'";
287 
288  // ** repeating_parameters used to create transhdr entry
289  $parameters = "'{$pValues['txParameters']}'";
290  // ** txn_data used to create transdtl entry
291  $data = "'{$pValues['txData']}'";
292 
293  // ** build sql insert query
294  $sqlColumns = "
295  cu, name,
296  feature_code,
297  user_id,
298  create_date,
299  approved_by,
300  approved_date,
301  last_edit_by,
302  last_edit_date,
303  start_date,
304  end_date,
305  next_trigger_date,
306  interval_count,
307  failure_count,
308  status,
309  repeating_parameters,
310  txn_data,
311  approved_status";
312  $sqlValues = "
313  '$cu', $name,
314  '$featureCode',
315  $userID,
316  $created,
317  $approvedBy,
318  $approvedDate,
319  $editedBy,
320  $editedDate,
321  $trigger,
322  $end,
323  $trigger,
324  $intervalCount,
325  $intervalFailure,
326  $status,
327  $parameters,
328  $data,
329  $approvedStatus";
330  $sqlCreate = "INSERT INTO cu_scheduledtxn
331  ($sqlColumns)
332  VALUES ($sqlValues)";
333  $sqlCreateRs = db_query($sqlCreate, $pDbh);
334  if (!$sqlCreateRs) {
335  $retStatusAry['status']['code'] = "999";
336  $retStatusAry['status']['severity'] = "ERROR";
337  $retStatusAry['status']['errors'][] = $pMC->msg("Transfer Save Error", HCU_DISPLAY_AS_RAW);
338  }
339 
340  return $retStatusAry;
341 }
342 
343 /**
344  * TxnSchedUpdate:
345  * @uses update a repeating scheduled transfer
346  *
347  * @param $pEnv - HB_ENV values
348  * @param $pDbh - database object
349  * @param $pMC - dictionary object
350  * @param $pValues - transaction object to update
351  *
352  * @return $retStatusAry - status of request
353  */
354 function TxnSchedUpdate($pEnv, $pDbh, $pMC, $pValues) {
355  $retStatusAry = Array(
356  'status' => Array('code'=>'000', 'errors' => Array()),
357  'data' => ''
358  );
359  /**
360  * must retrieve permission for this feature
361  * when creating a scheduled record, the approval cannot
362  * be set if the user needs confirmation from another
363  * source.
364  */
365  $confirmInputs = array("feature" => $pValues['txFeature']);
366  $confirmRequired = Perm_CheckConfirmReq($pDbh, $pEnv, $confirmInputs);
367  /**
368  * UPDATABLE FIELDS
369  *
370  * approved_by, approved_date
371  * last_edit_by, last_edit_date
372  * start_date, end_date, next_trigger_date
373  * status,
374  * repeating_parameters, txn_data
375  */
376  $id = $pValues['txId'];
377  $cu = prep_save($pEnv['Cu'], 10);
378 
379  // ** altered start/end/trigger dates
380  $start = $pValues['txDateStart'] == "" ? "NULL" : "'{$pValues['txDateStart']}'";
381  if ( $pValues['txFrequency'] == "OneTime" ) {
382  $end = "'{$pValues['txDateStart']}'";
383  } else {
384  $end = $pValues['txDateEnd'] == "" ? "NULL" : "'{$pValues['txDateEnd']}'";
385  }
386 
387  $trigger = "NULL";
388  $trigger = TxNextInterval(
389  $pValues['txDateStart'],
390  $pValues['txFrequency'],
391  $pValues['txFrequencyCount']);
392  $trigger = $trigger ? "'" . $trigger . "'" : "NULL";
393 
394  // ** setup interval count
395  $intervalCount = intval($pValues['txFrequencyCount']);
396 
397  // ** check permissions, set null if confirmation is needed
398  $approvedDate = $confirmRequired ? "NULL" : "CURRENT_TIMESTAMP" ;
399  $approvedBy = $confirmRequired ? "NULL" : intval($pEnv['Uid']) ;
400 
401  // ** creation is the first edit, set to this user
402  $editedBy = intval($pEnv['Uid']);
403  $editedDate = "CURRENT_TIMESTAMP";
404 
405  // ** active or inactive scheduled transfer
406  $status = "'{$pValues['txStatus']}'";
407 
408  // ** repeating_parameters used to create transhdr entry
409  $parameters = "'{$pValues['txParameters']}'";
410  // ** txn_data used to create transdtl entry
411  $data = "'{$pValues['txData']}'";
412 
413  // ** build sql insert query
414  $sqlColumns = "
415  approved_by,
416  approved_date,
417  last_edit_by,
418  last_edit_date,
419  start_date,
420  end_date,
421  next_trigger_date,
422  interval_count,
423  status,
424  repeating_parameters,
425  txn_data";
426  $sqlValues = "
427  $approvedBy,
428  $approvedDate,
429  $editedBy,
430  $editedDate,
431  $start,
432  $end,
433  $trigger,
434  $intervalCount,
435  $status,
436  $parameters,
437  $data";
438  $sqlUpdate = "
439  UPDATE cu_scheduledtxn
440  SET ($sqlColumns) = ($sqlValues)
441  WHERE cu = '$cu'
442  AND id = $id";
443  $sqlUpdateRs = db_query($sqlUpdate, $pDbh);
444  if (!$sqlUpdateRs) {
445  $retStatusAry['status']['code'] = "999";
446  $retStatusAry['status']['severity'] = "ERROR";
447  $retStatusAry['status']['errors'][] = $pMC->msg("Transfer Save Error", HCU_DISPLAY_AS_RAW);
448  }
449 
450  return $retStatusAry;
451 }
452 
453 /**
454  * TxnSchedDelete:
455  * @uses delete a repeating scheduled transfer
456  *
457  * @param $pEnv - HB_ENV values
458  * @param $pDbh - database object
459  * @param $pMC - dictionary object
460  * @param $pValues - transaction object to remove
461  *
462  * @return $retStatusAry - status of request
463  */
464 function TxnSchedDelete($pEnv, $pDbh, $pMC, $pValues) {
465  $retStatusAry = Array(
466  'status' => Array('code'=>'000', 'errors' => Array()),
467  'data' => ''
468  );
469 
470  /*
471  * DELETE by CU and ID
472  */
473  $id = $pValues['txId'];
474  $cu = prep_save($pEnv['Cu'], 10);
475 
476  // ** build sql statement
477  $sqlDelete = "
478  DELETE FROM cu_scheduledtxn
479  WHERE cu = '$cu'
480  AND id = $id";
481  $sqlDeleteRs = db_query($sqlDelete, $pDbh);
482  if (!$sqlDeleteRs) {
483  $retStatusAry['status']['code'] = "999";
484  $retStatusAry['status']['severity'] = "ERROR";
485  $retStatusAry['status']['errors'][] = $pMC->msg("Transfer Delete Fail", HCU_DISPLAY_AS_RAW);
486  }
487 
488  return $retStatusAry;
489 }
490 
491 /**
492  * TxnSchedParameters:
493  * @uses generate a json list of paraeters which will be stored in the
494  * cu_scheduledtxn table and used to generate the next trigger date.
495  *
496  * @param $pValues - transaction data, containing the transaction information
497  *
498  * @return $paramSTRING - json string of parameters
499  */
500 function TxnSchedParameters($pValues) {
501  $paramJSON = array(
502  "interval" => $pValues['txFrequency']
503  );
504  $paramSTRING = HCU_JsonEncode($paramJSON);
505  return $paramSTRING;
506 }
507 
508 /**
509  * TxnSchedData:
510  * @uses generate a json list of data which will be stored in the cu_scheduledtxn
511  * table and used to generate entries for the (cu)transhrd/dtl tables for
512  * internal transactions.
513  *
514  * @param $pValues - transaction data, containing the transaction information
515  *
516  * @return $dataSTRING - json string of data
517  */
518 function TxnSchedData( $pValues ) {
519  // see what type of operation is occuring
520  if ( $pValues["txFromType"] == 'X' || $pValues["txToType"] == 'X' ||
521  $pValues["txToType"] == 'M' ) {
522  // external or M2M, now figure out direction
523  if ( $pValues["txFromType"] == 'X' ) {
524  // from external to local
525  $fromParts = explode( "|", $pValues["txFromAcctId"] );
526  $from = $fromParts[2];
527  $frommember = "";
528  $fromsuffix = "";
529  $fromtype = $pValues["txMemType"];
530  $to = $pValues['txToAcctId'];
531  $tomember = $pValues['txToMember'];
532  $tosuffix = $pValues['txToSuffix'];
533  $type = "X2L";
534  } else {
535  // from local to external/m2m
536  $from = $pValues['txFromAcctId'];
537  $frommember = $pValues['txFromMember'];
538  $fromsuffix = $pValues['txFromSuffix'];
539  $fromtype = $pValues['txFromType'];
540  $toParts = explode( "|", $pValues["txToAcctId"] );
541  $to = $toParts[2]; // this is the extacct table id
542  $tomember = "";
543  $tosuffix = "";
544  $type = $pValues["txToType"] == 'M' ? "L2M" : "L2X";
545  }
546 
547  $dataJSON = array(
548  "txn" => array(
549  "from" => $from,
550  "frommember" => $frommember,
551  "fromsuffix" => $fromsuffix,
552  "fromtype" => $fromtype,
553  "to" => $to,
554  "tomember" => $tomember,
555  "tosuffix" => $tosuffix,
556  "totype" => $pValues['txToType'],
557  "amount" => $pValues['txAmount'],
558  "transactioncode" => $pValues['txCode'],
559  "include_memo" => "", // future need - include memo in ach addenda
560  "memo" => prep_save($pValues['txMemo']),
561  "type" => $type // this is really just to make it easier to understand when looking at database table and because TRNEXT txns can go either direction
562  )
563  );
564 } else {
565 
566  // Slight difference with cross accounts.
567  $tomember = $pValues['txToMember'];
568  if ($pValues["txTrust"] == "transfer") {
569  $toParts = explode( "|", $pValues["txToAcctId"] );
570  $tomember = $toParts[1];
571  }
572 
573  // traditional internal transfer
574  $dataJSON = array(
575  "txn" => array(
576  "from" => $pValues['txFromAcctId'],
577  "frommember" => $pValues['txFromMember'],
578  "fromsuffix" => $pValues['txFromSuffix'],
579  "fromtype" => $pValues['txFromType'],
580  "to" => $pValues['txToAcctId'],
581  "tomember" => $tomember,
582  "tosuffix" => $pValues['txToSuffix'],
583  "totype" => $pValues['txToType'],
584  "amount" => $pValues['txAmount'],
585  "transactioncode" => $pValues['txCode'],
586  "deposittype" => $pValues['txFromType'],
587  "memo" => prep_save($pValues['txMemo'])
588  )
589  );
590  }
591 
592 
593  $dataSTRING = HCU_JsonEncode($dataJSON);
594  return $dataSTRING;
595 } // end TxnSchedData
596 
597 /**
598  * TxnSchedDataACH:
599  * @uses generate a JSON list of data which will be stored in the cu_scheduledtxn
600  * table and used to generate entries for the (cu)transhdr/dtl tables for
601  * ACH transactions.
602  *
603  * @param $pValues - transaction data, containing the transaction information
604  *
605  * @return $dataSTRING - JSON string of data
606  */
607 function TxnSchedDataACH( $pValues ) {
608 
609  $dataJSON = array(
610  "txn" => array(
611  "amount" => $pValues['txAmount'],
612  "transactioncode" => $pValues['txCode'],
613  "include_memo" => "", // future need - include memo in ach addenda
614  "memo" => prep_save($pValues['txMemo']),
615  "addenda" => $pValues['txAddenda'],
616  "type" => $pValues['txType']
617  )
618  );
619 
620  $acctId = explode("|", $pValues['txLocalAccount']);
621  if ($pValues['txType'] == "L2R") {
622  $dataJSON['txn']['from'] = $pValues['txLocalAccount'];
623  $dataJSON['txn']['frommember'] = $pValues['txLocalMember'];
624  $dataJSON['txn']['fromsuffix'] = $acctId[2]; // account type
625  $dataJSON['txn']['fromtype'] = $acctId[0]; // record type
626  $dataJSON['txn']['to'] = $pValues['txPartnerId'];
627  } else {
628  $dataJSON['txn']['to'] = $pValues['txLocalAccount'];
629  $dataJSON['txn']['tomember'] = $pValues['txLocalMember'];
630  $dataJSON['txn']['tosuffix'] = $acctId[2]; // account type
631  $dataJSON['txn']['totype'] = $acctId[0]; // record type
632  $dataJSON['txn']['from'] = $pValues['txPartnerId'];
633  }
634 
635  $dataSTRING = HCU_JsonEncode($dataJSON);
636  return $dataSTRING;
637 } // end TxnSchedDataACH
638 
639 /**
640  * TxIntervalList:
641  * @uses retrieve a list of all possible scheduled transfer
642  * intervals
643  *
644  * @param $MC -- represents the dictionary object
645  * @return list of possible scheduled transfer intervals
646  */
647 function TxIntervalList($MC) {
648  return Array(
649  Array("value" => "OneTime", "text" => str_replace("#", "\\#", $MC->msg("TRNSCHED OneTime"))),
650  Array("value" => "FirstDayOfMonth", "text" => str_replace("#", "\\#", $MC->msg("TRNSCHED FirstDayOfMonth"))),
651  Array("value" => "LastDayOfMonth", "text" => str_replace("#", "\\#", $MC->msg("TRNSCHED LastDayOfMonth"))),
652  Array("value" => "FirstFifteenth", "text" => str_replace("#", "\\#", $MC->msg("TRNSCHED FirstFifteenth"))),
653  Array("value" => "FifteenthLast", "text" => str_replace("#", "\\#", $MC->msg("TRNSCHED FifteenthLast"))),
654  Array("value" => "Weekly", "text" => str_replace("#", "\\#", $MC->msg("TRNSCHED Weekly"))),
655  Array("value" => "BiWeekly", "text" => str_replace("#", "\\#", $MC->msg("TRNSCHED BiWeekly"))),
656  Array("value" => "WeekDayOfMonth", "text" => str_replace("#", "\\#", $MC->msg("TRNSCHED WeekDayOfMonth"))),
657  Array("value" => "Monthly", "text" => str_replace("#", "\\#", $MC->msg("TRNSCHED Monthly"))),
658  Array("value" => "Every2Months", "text" => str_replace("#", "\\#", $MC->msg("TRNSCHED Every2Months"))),
659  Array("value" => "Quarterly", "text" => str_replace("#", "\\#", $MC->msg("TRNSCHED Quarterly"))),
660  Array("value" => "SemiAnnually", "text" => str_replace("#", "\\#", $MC->msg("TRNSCHED SemiAnnually"))),
661  Array("value" => "Annually", "text" => str_replace("#", "\\#", $MC->msg("TRNSCHED Annually")))
662  );
663 }
664 
665 /**
666  * TxContinueList:
667  * @uses return a list of the "Continue Until" choices to end the repeating transfer.
668  *
669  * @param $MC -- represents the dictionary object
670  * @return list of possible ending choices.
671  */
672 function TxContinueList($MC) {
673  return Array(
674  Array("value" => "continuous", "text" => $MC->msg('Transfer Continue Until', HCU_DISPLAY_AS_RAW) . ' ' .$MC->msg('Transfer Further Notice', HCU_DISPLAY_AS_RAW)),
675  Array("value" => "continueuntil", "text" => $MC->msg('Transfer Continue Until', HCU_DISPLAY_AS_RAW))
676  );
677 }
678 
679 /**
680  * TxNextInterval:
681  * @uses calculate the next trigger date for a particular transaction
682  *
683  * @param $pStart - calculate the next trigger date starting at this date
684  * @param $pEnd - next trigger date cannot go past this date
685  * @param $pInterval - the interval for which to calculate the next trigger date.
686  *
687  * @return $dateTrigger - next trigger date: will be null if calculation is past the pEnd date.
688  */
689 function TxNextInterval($pStart, $pInterval, $pIntervalCount) {
690 
691  $dateTrigger = null;
692  $dateStart = strtotime($pStart);
693 
694  // set current day/month/year
695  $cDay = date("j", $dateStart);
696  $cMonth = date("m", $dateStart);
697  $cYear = date("Y", $dateStart);
698  $cInterval = intval($pIntervalCount);
699 
700  // ** switch interval for calculation
701  switch ($pInterval) {
702  case "OneTime":
703  // ** Increment the Next Trigger date
704  // ** One time only runs once, incrementing the interval should have NO impact.
705  $dateTrigger = date("m/d/Y", $dateStart + (($cInterval > 0 ? 1 : 0) * 86400));
706  break;
707  case "FirstDayOfMonth":
708  /* if the starting day is greater than 1
709  * increment the month to next month.
710  *
711  * always add the interval to the month
712  * to ensure the correct month based on
713  * how many intervals have already occurred.
714  */
715  $cMonth = ($cDay > 1) ? $cMonth + 1 : $cMonth;
716  $cMonth = $cMonth + $cInterval;
717 
718  $nextFirst = mktime(0,0,0, $cMonth, 1, $cYear);
719  $nextFirst = date("m/d/Y", $nextFirst);
720  $dateTrigger = $nextFirst;
721  break;
722  case "LastDayOfMonth":
723  /* since we are looking for the last day of the month
724  * there is no need to add one prior to adding the interval
725  */
726  $cMonth = $cMonth + $cInterval;
727 
728  $nextLast = mktime(0,0,0, $cMonth, 1, $cYear);
729  $nextLast = date("m/t/Y", $nextLast);
730  $dateTrigger = $nextLast;
731  break;
732  case "FirstFifteenth":
733  /**
734  * if the interval count is greater than 0
735  * calculate the next trgger date off of the start date
736  * plus the number of intervals.
737  *
738  * start with by finding the initial date
739  * being a 1st or 15th of the month
740  *
741  * if the current date is 1
742  * set day to 15 for next iteration
743  * if the current date is 15
744  * set day to 1 for next iteration
745  * set month + 1 for next iteration
746  *
747  * this should run for the total number of intervals that
748  * have actually occurred in order to get the correct next
749  * trigger date.
750  */
751 
752  $startDay = ($cDay > 15 || $cDay == 1) ? 1 : 15;
753  $startMonth = $cMonth + ($cDay > 15);
754 
755  while ($cInterval > 0) {
756  if ($startDay == 1) {
757  $startDay = 15;
758  } else {
759  $startDay = 1;
760  $startMonth ++;
761  if ($startMonth > 12) {
762  $startMonth = 1;
763  $cYear ++;
764  }
765  }
766 
767  $cInterval --;
768  }
769 
770  $nextDate = mktime(0,0,0, $startMonth, $startDay, $cYear);
771  $dateTrigger = date("m/d/Y", $nextDate);
772 
773  break;
774  case "FifteenthLast":
775  /**
776  * if the interval count is greater than 0
777  * calculate the next trgger date off of the start date
778  * plus the number of intervals.
779  *
780  * start with by finding the initial date
781  * being a 15th or last day of the month
782  *
783  * if the current date is 15
784  * set day to last day for next iteration
785  * if the current date is last day
786  * set day to 15 for next iteration
787  * set month + 1 for next iteration
788  *
789  * this should run for the total number of intervals that
790  * have actually occurred in order to get the correct next
791  * trigger date.
792  */
793  $startDay = ($cDay <= 15) ? 15 : date("t", $dateStart);
794  $startMonth = $cMonth;
795 
796  while ($cInterval > 0) {
797  if ($startDay == 15) {
798  $startDay = strtotime($startMonth . "/1/" . $cYear);
799  $startDay = date("t", $startDay);
800  } else {
801  $startDay = 15;
802  $startMonth ++;
803  if ($startMonth > 12) {
804  $startMonth = 1;
805  $cYear ++;
806  }
807  }
808 
809  $cInterval --;
810  }
811 
812  $nextDate = mktime(0,0,0, $startMonth, $startDay, $cYear);
813  $dateTrigger = date("m/d/Y", $nextDate);
814 
815  break;
816  case "Weekly":
817  /* one week is 7 days, multiply this by each interval */
818  $cDay = $cDay + (7 * $cInterval);
819 
820  $nextWeek = mktime(0,0,0, $cMonth, $cDay, $cYear);
821  $nextWeek = date("m/d/Y", $nextWeek);
822  $dateTrigger = $nextWeek;
823  break;
824  case "BiWeekly":
825  /* two weeks is 14 days, multiply this by each interval */
826  $cDay = $cDay + (14 * $cInterval);
827 
828  $nextBiWeek = mktime(0,0,0, $cMonth, $cDay, $cYear);
829  $nextBiWeek = date("m/d/Y", $nextBiWeek);
830  $dateTrigger = $nextBiWeek;
831  break;
832  case "WeekDayOfMonth":
833  // e.g. third thursday of the month
834  // e.g. fouth saturday of the month
835  $weekDay = date("l", $dateStart);
836  $week = ceil($cDay / 7);
837 
838  $weekNumber = "";
839  switch ($week) {
840  case 1:
841  $weekNumber = "first";
842  break;
843  case 2:
844  $weekNumber = "second";
845  break;
846  case 3:
847  $weekNumber = "third";
848  break;
849  case 4:
850  $weekNumber = "fourth";
851  break;
852  case 5:
853  $weekNumber = "last";
854  break;
855  }
856 
857  $monthStart = mktime(0,0,0, $cMonth + $cInterval, 1, $cYear);
858  $monthName = date("M", $monthStart);
859  $monthYear = date("Y", $monthStart);
860 
861  // format of this string: first saturday of june 2017
862  $nextDayOfMonth = "";
863  $nextDayOfMonth .= $weekNumber . " ";
864  $nextDayOfMonth .= $weekDay . " of ";
865  $nextDayOfMonth .= $monthName . " ";
866  $nextDayOfMonth .= $monthYear;
867 
868  $nextDay = strtotime($nextDayOfMonth);
869  $dateTrigger = date("m/d/Y", $nextDay);
870  break;
871  case "Monthly":
872  /* add interval to get correct month */
873  $cMonth = $cMonth + $cInterval;
874 
875  $nextMonth = mktime(0,0,0, $cMonth, 1, $cYear);
876  $nextMonthDays = date("t", $nextMonth);
877  $nextDay = null;
878  if ($cDay > $nextMonthDays) {
879  $nextDay = $nextMonthDays;
880  } else {
881  $nextDay = $cDay;
882  }
883 
884  $nextDayOfMonth = mktime(0,0,0, $cMonth, $nextDay, $cYear);
885  $nextDayOfMonth = date("m/d/Y", $nextDayOfMonth);
886  $dateTrigger = $nextDayOfMonth;
887  break;
888  case "Every2Months":
889  $cMonth = $cMonth + (2 * $cInterval);
890  while ($cMonth > 12) {
891  $cMonth = $cMonth - 12;
892  $cYear ++;
893  }
894 
895  $nextTwoMonth = mktime(0,0,0, $cMonth, 1, $cYear);
896  $nextTwoMonthDays = date("t", $nextTwoMonth);
897  $nextDay = null;
898  if ($cDay > $nextTwoMonthDays) {
899  $nextDay = $nextTwoMonthDays;
900  } else {
901  $nextDay = $cDay;
902  }
903 
904  $nextTwo = mktime(0,0,0, $cMonth, $nextDay, $cYear);
905  $nextTwo = date("m/d/Y", $nextTwo);
906  $dateTrigger = $nextTwo;
907  break;
908  case "Quarterly":
909  $cMonth = $cMonth + (3 * $cInterval);
910  while ($cMonth > 12) {
911  $cMonth = $cMonth - 12;
912  $cYear ++;
913  }
914 
915  $nextThreeMonth = mktime(0,0,0, $cMonth, 1, $cYear);
916  $nextThreeMonthDays = date("t", $nextThreeMonth);
917  $nextDay = null;
918  if ($cDay > $nextThreeMonthDays) {
919  $nextDay = $nextThreeMonthDays;
920  } else {
921  $nextDay = $cDay;
922  }
923 
924  $nextQuarter = mktime(0,0,0, $cMonth, $nextDay, $cYear);
925  $nextQuarter = date("m/d/Y", $nextQuarter);
926  $dateTrigger = $nextQuarter;
927  break;
928  case "SemiAnnually":
929  $cMonth = $cMonth + (6 * $cInterval);
930  while ($cMonth > 12) {
931  $cMonth = $cMonth - 12;
932  $cYear ++;
933  }
934 
935  $nextSixMonth = mktime(0,0,0, $cMonth, 1, $cYear);
936  $nextSixMonthDays = date("t", $nextSixMonth);
937  $nextDay = null;
938  if ($cDay > $nextSixMonthDays) {
939  $nextDay = $nextSixMonthDays;
940  } else {
941  $nextDay = $cDay;
942  }
943 
944  $nextSemi = mktime(0,0,0, $cMonth, $nextDay, $cYear);
945  $nextSemi = date("m/d/Y", $nextSemi);
946  $dateTrigger = $nextSemi;
947  break;
948  case "Annually":
949  /* add interval to get correct year */
950  $cYear = $cYear + $cInterval;
951 
952  $nextAnnual = mktime(0,0,0, $cMonth, 1, $cYear);
953  $nextAnnualDays = date("t", $nextAnnual);
954  $nextDay = null;
955  if ($cDay > $nextAnnualDays) {
956  $nextDay = $nextAnnualDays;
957  } else {
958  $nextDay = $cDay;
959  }
960 
961  $nextAnnual = mktime(0,0,0, $cMonth, $nextDay, $cYear);
962  $nextAnnual = date("m/d/Y", $nextAnnual);
963  $dateTrigger = $nextAnnual;
964  break;
965  }
966 
967 
968  return $dateTrigger;
969 }
970 
971 
972 // Get the partner info from the ach partner table.
973 function _GetACHPartnerDisplay( $pEnv, $pPartnerId ) {
974  $Cu = $pEnv["Cu"];
975 
976  $sql = "SELECT ap.display_name
977  FROM {$Cu}achpartner ap
978  WHERE ap.id = $pPartnerId";
979 
980  $rs = db_query( $sql, $pEnv["dbh"] );
981  $row = db_fetch_assoc( $rs );
982  $name = "ACH: {$row["display_name"]}";
983 
984  return $name;
985 } // end _GetACHPartnerDisplay
986 
987 // Get the partner info from the extaccount table.
988 function _GetExtPartnerDisplay( $pEnv, $pPartnerId ) {
989  $Cu = $pEnv["Cu"];
990 
991  $sql = "SELECT ea.display_name, type
992  FROM {$Cu}extaccount ea
993  WHERE ea.id = $pPartnerId";
994 
995  $rs = db_query( $sql, $pEnv["dbh"] );
996  $row = db_fetch_assoc( $rs );
997  $name = "{$row["type"]}: {$row["display_name"]}";
998 
999  return $name;
1000 } // end _GetExtPartnerDisplay
1001 
1002 function FindTransactionData($pDbh, $pId) {
1003  $sqlSelect = "
1004  SELECT txn_data, feature_code, start_date, repeating_parameters
1005  FROM cu_scheduledtxn
1006  WHERE id = $pId";
1007  $sqlSelectRs = db_query($sqlSelect, $pDbh);
1008  $sqlTxn = db_fetch_assoc($sqlSelectRs, 0);
1009 
1010  return $sqlTxn;
1011 }
1012 
1013 /**
1014  * GetCalendarData:
1015  * @uses this function servs to create arrays to hold all dates for
1016  * repeating transactions upto a specified date. the function also
1017  * creates an array of key/value pairs where each key is a date value
1018  * from the first array with values being each txId associated with it.
1019  *
1020  * @param pHBEnv - environment variable: HB_ENV values
1021  * @param pMC - dictionary object
1022  * @param pSchedules - list of scheduled repeating transfers
1023  * @param pStart - the list of transfers will contain dates >= to this
1024  * @param pEnd - the list of transfers will contain dates up to this
1025  *
1026  * @return scheduledReturn - list of dates and key/value pairs
1027  */
1028 function GetCalendarData($pHBEnv, $pMC, $pSchedules, $pStart, $pEnd) {
1029  /*
1030  * Create Schedule of Dates for the repeating transfers.
1031  *
1032  * Starting tomorrow going until $pMonths months from tomorrow create a list of dates
1033  * each scheduled transfer is requested
1034  *
1035  * start schedule tomorrow
1036  * end the schedule $pMonths months from tomorrow
1037  */
1038  $sdDateStart = $pStart;
1039  $sdTimeStart = strtotime($pStart);
1040  $sdDateEnd = $pEnd;
1041  $sdTimeEnd = strtotime($pEnd);
1042 
1043  /**
1044  * scheduledDates:
1045  * - list of all dates which have a transfer occurence
1046  *
1047  * scheduledOccur:
1048  * - list of key/value pairs
1049  * - keys = scheduledDates
1050  * - values = txid's associated with the key dates
1051  */
1052  $scheduleDates = array();
1053  $scheduleOccur = array();
1054  $scheduleValid = array();
1055  $scheduleReturn = array();
1056 
1057  // ** iterate schedules from tx read
1058  foreach ($pSchedules as $key => $value) {
1059  $txStatus = $value['txStatus'];
1060  $txApproved = $value['txApproved'];
1061  $txInterval = $value['txFrequency'];
1062  $txIntervalCount = $value['txFrequencyCount'];
1063 
1064  // get record next trigger date and end date in date and time formats
1065  // start the next date on the start date to include the start date in
1066  // the list
1067  $txTimeNext = strtotime($value['txDateNext']);
1068  $txDateNext = date("m/d/Y", $txTimeNext);
1069 
1070  $txDateEnd = null;
1071  $txTimeEnd = null;
1072 
1073  // set end time for this schedule if one exists
1074  // set end time to user defined end date if none exists
1075  if ($value['txDateEnd'] != "") {
1076  $txTimeEnd = strtotime($value['txDateEnd']);
1077  $txDateEnd = date("m/d/Y", $txTimeEnd);
1078 
1079  } else {
1080  $txDateEnd = $sdDateEnd;
1081  $txTimeEnd = $sdTimeEnd;
1082  }
1083 
1084  // ** Check for Inactive -- ONLY calculate for ACTIVE
1085  if ($txStatus == "A" && ($txApproved == 10 || $txApproved == 0)) {
1086 
1087  /*
1088  * evaluate records that have a trigger date greater or equal to tomorrow ($scheduleStart)
1089  */
1090  if ($txTimeNext >= $sdTimeStart) {
1091  /**
1092  * get next trigger dates for this record up to the scheduleEnd date
1093  * IntervalCalculate will retun null when the date to calculate from is greater
1094  * than the end date.
1095  *
1096  * start date will be the next calculated trigger date
1097  * end date will be the $scheduleEnd date or the end date of the record
1098  */
1099  while ($txDateNext != null) {
1100  $scheduleDates[] = returnJsonDateValue($txDateNext);
1101  $scheduleOccur[$txDateNext][] = $value['txId'];
1102  $txDateNext = TxNextInterval($txDateNext, $txInterval, $txIntervalCount);
1103 
1104  // if one time, break loop
1105  if ($txInterval == "OneTime") {
1106  break;
1107  }
1108 
1109  // if next date is after cutoff date
1110  // break loop.
1111  if (strtotime($txDateNext) < $txTimeEnd) {
1112  break;
1113  }
1114  }
1115  }
1116  }
1117 
1118  $scheduleValid[] = $value;
1119  }
1120 
1121  $scheduleReturn['valid'] = $scheduleValid;
1122  $scheduleReturn['dates'] = $scheduleDates;
1123  $scheduleReturn['occur'] = $scheduleOccur;
1124  return $scheduleReturn;
1125 } // end GetCalendarData
1126 
1127 /**
1128  * function SkipTrans($dbh, $Cu, $userId, $changeList, $MC)
1129  * This skips all selected scheduled transactions forward to the next trigger date (if it exists.)
1130  *
1131  * @param $dbh -- the database connection
1132  * @param $Cu -- the credit union
1133  * @param $userId -- the banking user
1134  * @param $changeList -- a JSON list of ids to change
1135  * @param $MC -- the dictionary (since I am using a DDL from the banking side, I need this to translate into English.)
1136  *
1137  * @return "status" -- "000" if successful, nonzero otherwise
1138  * @return "error" -- "" if successful, nonempty otherwise
1139  * @return "repeatingTransfers" -- a list of repeating transfers
1140  * @return "info" -- if successful, give a success message
1141  */
1142 function SkipTrans($dbh, $Cu, $userId, $changeList, $MC) {
1143  try {
1144  if ($changeList == "") {
1145  throw new exception("Nothing to skip.", 201);
1146  }
1147  $changeList = HCU_JsonDecode($changeList);
1148  if (!is_array($changeList)) {
1149  throw new exception("Change List is malformed.", 202);
1150  }
1151  if (count($changeList) == 0) {
1152  throw new exception("Nothing to skip.", 207);
1153  }
1154  foreach($changeList as $changeListId) {
1155  if (!is_integer($changeListId)) {
1156  throw new exception("Change List is malformed.", 203);
1157  }
1158  }
1159 
1160  // SQL to check that records are valid for skipping.
1161  $sql = "select id, approved_status, repeating_parameters::json->>'interval' as interval, interval_count, status, next_trigger_date, end_date, start_date from cu_scheduledtxn
1162  where cu = '$Cu' and user_id = $userId and id in (" . implode(", ", $changeList) . ")";
1163  $sth = db_query($sql, $dbh);
1164  if (!$sth) {
1165  throw new exception("Select query failed.", 209);
1166  }
1167  $updateList = array();
1168  for($i = 0; $row = db_fetch_assoc($sth, $i); $i++) {
1169  if ($row["status"] != "A") {
1170  throw new exception("Skips can only happen on active records.", 204);
1171  }
1172  if ($row["approved_status"] != 10) {
1173  throw new exception("Skips can only happen on approved records.", 205);
1174  }
1175  $id = $row["id"];
1176 
1177  if (!isset($row["next_trigger_date"])) {
1178  throw new exception("Skips cannot happen on records with a null next trigger date.", 209);
1179  }
1180 
1181  $nextInterval = TxNextInterval($row["start_date"], $row["interval"], $row["interval_count"] + 1);
1182 
1183  if (isset($nextInterval)) {
1184  $nextInterval = explode("/", $nextInterval);
1185  $nextInterval = "'" . $nextInterval[2] . "-" . $nextInterval[0] . "-" . $nextInterval[1] . "'"; // Get into the format needed for the database.
1186  } else {
1187  $nextInterval = "null";
1188  }
1189 
1190  if ($nextInterval != "null" && isset($row["end_date"]) && $nextInterval > "'" . $row["end_date"] . "'") {
1191  $nextInterval = "null";
1192  }
1193  $updateList[$id] = "($id, $nextInterval)";
1194  }
1195  if (count($updateList) != count($changeList)) { // Some ids do not exist or are attached to the wrong user/Cu.
1196  throw new exception("Some ids in the changeList are invalid.", 206);
1197  }
1198  unset($changeList);
1199 
1200  $sql = "update cu_scheduledtxn set next_trigger_date = b.next_trigger_date::date, interval_count = interval_count + 1
1201  from (values " . implode(", ", $updateList) . ") as b (id, next_trigger_date) where cu_scheduledtxn.id = b.id";
1202  $sth = db_query($sql, $dbh);
1203  if (!$sth) {
1204  throw new exception("Update query failed.", 208);
1205  }
1206 
1207  $readResults = ReadTrans($dbh, $Cu, $userId, $MC);
1208 
1209  if ($readResults["status"] != "000") {
1210  throw new exception($readResults["error"][0], $readResults["code"]);
1211  }
1212  $repeatingTransfers = $readResults["repeatingTransfers"];
1213  unset($readResults);
1214 
1215  $returnArray = array("status" => "000", "error" => "", "repeatingTransfers" => $repeatingTransfers,
1216  "info" => (count($updateList) == 1 ? "Transfer was " : "Transfers were ") . "skipped successfully.");
1217  } catch(exception $e) {
1218  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
1219  }
1220  return $returnArray;
1221 }
1222 
1223 /**
1224  * function ReadTrans($dbh, $Cu, $userId, $MC)
1225  * This reads the scheduled transactions for the banking user that are approved.
1226  *
1227  * @param $dbh -- the database connection
1228  * @param $Cu -- the credit union
1229  * @param $userId -- the banking user
1230  * @param $MC -- the dictionary (since I am using a DDL from the banking side, I need this to translate into English.)
1231  *
1232  * @return "status" -- "000" if successful, nonzero otherwise
1233  * @return "error" -- "" if successful, nonempty otherwise
1234  * @return "repeatingTransfers" -- a list of repeating transfers
1235  */
1236 function ReadTrans($dbh, $Cu, $userId, $MC) {
1237  try {
1238  $intervalList = TxIntervalList($MC);
1239  $intervalLookup = array();
1240  foreach($intervalList as $intervalRecord) {
1241  $intervalLookup[$intervalRecord["value"]] = $intervalRecord["text"];
1242  }
1243  unset($intervalList);
1244 
1245  // Then once the list of the accounts is retrieved from the primary user, then the rights for the subaccounts are retrieved from the actual user.
1246  $subqueryTXN = "select ss.id, ss.feature_code, ss.feature, ss.create_date, ss.start_date, ss.end_date, ss.next_trigger_date, ss.status, ss.repeating_parameters,
1247  ss.txn_data, ss.failure_count, case when strpos(ss.fromkey, '|') = 0 then cast(ss.fromkey as text)
1248  when coalesce(strpos(ss.fromsuffix, '@'), '0') <> 0 then trim(split_part(fromkey, '|', 1)) || trim(ss.frommember)
1249  || trim(ss.fromsuffix) || coalesce(nullif(split_part(fromkey, '|', 4), ''), '0')
1250  else trim(split_part(fromkey, '|', 1)) || trim(split_part(fromkey, '|', 2)) || trim(split_part(fromkey, '|', 3)) || coalesce(nullif(split_part(fromkey, '|', 4), ''), '0') end
1251  as fromkey,
1252  case when strpos(ss.tokey, '|') = 0 then cast(ss.tokey as text)
1253  when coalesce(strpos(ss.tosuffix, '@'), 0) <> 0 then trim(split_part(tokey, '|', 1)) || trim(ss.tomember) || trim(ss.tosuffix) || coalesce(nullif(split_part(tokey, '|', 4), ''), '0')
1254  when ss.code not in ('XA', 'XP') then trim(split_part(tokey, '|', 1)) || trim(split_part(tokey, '|', 2)) || trim(split_part(tokey, '|', 3)) ||
1255  coalesce(nullif(split_part(tokey, '|', 4), ''), '0')
1256  else case when trim(split_part(tokey, '|', 1)) = 'D' then 'T' else 'P' end || frommember || trim(split_part(tokey, '|', 3)) || '#' || trim(split_part(tokey, '|', 2))
1257  || coalesce(nullif(split_part(tokey, '|', 4), ''), '0') end as tokey
1258  from (
1259  select s.id, s.feature_code, f.description as feature, s.create_date, s.start_date, s.end_date, s.next_trigger_date, s.status, s.repeating_parameters, s.txn_data,
1260  s.failure_count,
1261  replace(s.txn_data::json->'txn'->>'from', 'C|', 'L|') as fromkey,
1262  replace(s.txn_data::json->'txn'->>'to', 'C|', 'L|') as tokey,
1263  s.txn_data::json->'txn'->>'transactioncode' as code,
1264  s.txn_data::json->'txn'->>'frommember' as frommember, s.txn_data::json->'txn'->>'fromsuffix' as fromsuffix, s.txn_data::json->'txn'->>'tomember' as tomember,
1265  s.txn_data::json->'txn'->>'tosuffix' as tosuffix
1266  from cu_scheduledtxn s
1267  inner join cu_feature f on s.feature_code = f.feature_code and s.cu = '$Cu' and f.enabled and approved_status = 10
1268  where s.user_id = $userId
1269  ) ss";
1270 
1271  $subqueryALXB = "select 'D' || trim(accountnumber) || trim(accounttype) || certnumber as key, may_deposit, may_withdraw,
1272  accountnumber, accounttype, certnumber, description
1273  from ${Cu}accountbalance
1274  union all
1275  select 'T' || trim(accountnumber) || trim(accounttype) || '#' || trim(tomember) || '0' as key, true, false, tomember, accounttype, 0, description
1276  from ${Cu}crossaccounts where deposittype not in ('L', 'C')
1277  union all
1278  select 'L' || trim(accountnumber) || trim(loannumber) || '0' as key, may_payment, may_addon, accountnumber, loannumber, 0, description
1279  from ${Cu}loanbalance
1280  union all
1281  select 'P' || trim(accountnumber) || trim(accounttype) || '#' || trim(tomember) || '0' as key, false, false, tomember, accounttype, 0, description
1282  from ${Cu}crossaccounts where deposittype in ('L', 'C')";
1283 
1284  $sql = "select txn.*, alxbfrom.description as from_desc, alxbfrom.accountnumber as from_acct, alxbfrom.accounttype as from_accounttype,
1285  alxbfrom.certnumber as from_certnumber, alxbto.description as to_desc, alxbto.accountnumber as to_acct,
1286  alxbto.accounttype as to_accounttype, alxbto.certnumber as to_certnumber from ($subqueryTXN) txn
1287  left join ($subqueryALXB) as alxbfrom on txn.fromkey::text = alxbfrom.key::text
1288  left join ($subqueryALXB) as alxbto on txn.tokey::text = alxbto.key::text
1289  order by txn.next_trigger_date";
1290 
1291  $sth = db_query($sql, $dbh);
1292  if (!$sth) {
1293  throw new exception("Select query failed.", 101);
1294  }
1295  $repeatingTransfers = array();
1296 
1297  $achcols = array();
1298  $achpmts = array();
1299  $exts = array();
1300 
1301  for($i = 0; $row = db_fetch_assoc($sth, $i); $i++) {
1302  switch($row["feature_code"]) {
1303  case "ACHCOL":
1304  $achcols[] = $row["id"];
1305  break;
1306  case "ACHPMT":
1307  $achpmts[] = $row["id"];
1308  break;
1309  case "TRNEXT":
1310  case "TRNM2M":
1311  $exts[] = $row["id"];
1312  break;
1313  }
1314 
1315  $repeatingTransfers[] = $row;
1316  }
1317 
1318  $sql = array();
1319  $achMap = array();
1320  if (count($achcols) > 0) {
1321  $sql[] = "select s.id as data_id, ap.ach_name, ap.display_name, ap.address, ap.dfi_data from cu_scheduledtxn s
1322  inner join ${Cu}achpartner ap on s.feature_code = 'ACHCOL' and cast(s.txn_data::json->'txn'->>'from' as int) = ap.id where s.id in (" . implode(", ", $achcols) . ")";
1323  }
1324  if (count($achpmts) > 0) {
1325  $sql[] = "select s.id as data_id, ap.ach_name, ap.display_name, ap.address, ap.dfi_data from cu_scheduledtxn s
1326  inner join ${Cu}achpartner ap on s.feature_code = 'ACHPMT' and cast(s.txn_data::json->'txn'->>'to' as int) = ap.id where s.id in (" . implode(", ", $achpmts) . ")";
1327  }
1328 
1329  if (count($sql) > 0) {
1330  $sql = implode(" union all ", $sql);
1331  $sth = db_query($sql, $dbh);
1332  if (!$sth) {
1333  throw new exception("ach map query failed.", 307);
1334  }
1335  for($i = 0; $row = db_fetch_assoc($sth, $i); $i++) {
1336  $row["dfi_data"] = HCU_JsonDecode($row["dfi_data"]);
1337  $row["address"] = HCU_JsonDecode($row["address"]);
1338 
1339  $achMap[$row["data_id"]] = $row;
1340  }
1341  }
1342 
1343  $sql = array();
1344  $extMap = array();
1345  if (count($exts) > 0) {
1346  $sql[] = "select s.id as data_id, 'from' as dir, ea.display_name, ea.remote_info from cu_scheduledtxn s
1347  inner join ${Cu}extaccount ea on s.feature_code in ('TRNEXT') and s.txn_data::json->'txn'->>'type' in ('X2L') and cast(s.txn_data::json->'txn'->>'from' as int) = ea.id
1348  and ea.type = 'EXT' where s.id in (" . implode(", ", $exts) . ")"; // Member-to-member can only come from the local account.
1349  $sql[] = "select s.id as data_id, 'to', ea.display_name, ea.remote_info from cu_scheduledtxn s
1350  inner join ${Cu}extaccount ea on s.feature_code in ('TRNEXT', 'TRNM2M') and s.txn_data::json->'txn'->>'type' in ('L2X', 'L2M') and replace(s.feature_code, 'TRN', '') = ea.type
1351  and cast(s.txn_data::json->'txn'->>'to' as int) = ea.id where s.id in (" . implode(", ", $exts) . ")";
1352  }
1353 
1354  if (count($sql) > 0) {
1355  $sql = implode(" union all ", $sql);
1356  $sth = db_query($sql, $dbh);
1357  if (!$sth) {
1358  throw new exception("ext map query failed.", 308);
1359  }
1360  for($i = 0; $row = db_fetch_assoc($sth, $i); $i++) {
1361  $row["remote_info"] = HCU_JsonDecode($row["remote_info"]);
1362 
1363  $extMap[$row["data_id"]] = $row;
1364  }
1365  }
1366 
1367  $mask = GetMask($dbh, $Cu)["data"];
1368  for($i = 0, $iCount = count($repeatingTransfers); $i != $iCount; $i++) {
1369  $row = $repeatingTransfers[$i];
1370 
1371  $repeating = isset($row["repeating_parameters"]) ? trim($row["repeating_parameters"]) : "";
1372  if ($repeating != "") {
1373  $repeating = HCU_JsonDecode($repeating);
1374  if (!is_array($repeating)) {
1375  throw new exception("Repeating is malformed.", 102);
1376  }
1377  if (!HCU_array_key_exists("interval", $repeating)) {
1378  throw new exception("Repeating is malformed.", 103);
1379  }
1380  $row["interval"] = $repeating["interval"];
1381  $row["intervalText"] = HCU_array_key_exists($row["interval"], $intervalLookup) ? $intervalLookup[$row["interval"]] : "";
1382  }
1383  unset($row["repeating_parameters"]);
1384 
1385  $txnData = isset($row["txn_data"]) ? trim($row["txn_data"]) : "";
1386  $flagset3 = GetFlagsetValue('CU3_PREPEND_MBR_DESC');
1387  $row["details"] = ""; // Ensure that this always exists.
1388  if ($txnData != "") {
1389  $txnData = HCU_JsonDecode($txnData);
1390  if (!is_array($txnData)) {
1391  throw new exception("TxnData is malformed.", 104);
1392  }
1393 
1394  // Truncated version of online banking activity templates. Normally, kendo takes care of the entities but I am turning encoded off on the column to allow for <br>.
1395  switch($row["feature_code"]) {
1396  case "TRN":
1397  $row["details"] = "<b>From</b> " .
1398  getAccountDescription($dbh, $Cu, $row["from_acct"], $row["from_desc"], $row["from_accounttype"], "", $flagset3, $row["from_certnumber"])
1399  . "<br> <b>To</b> " .
1400  getAccountDescription($dbh, $Cu, $row["to_acct"], $row["to_desc"], $row["to_accounttype"], "", $flagset3, $row["to_certnumber"]);
1401  break;
1402  case "ACHCOL":
1403  $partnerRow = $achMap[$row["id"]];
1404  $routing = trim($partnerRow["dfi_data"]["dfi_routing"]);
1405  $account = trim($partnerRow["dfi_data"]["dfi_account"]);
1406  $from = $routing . " / " . ApplyMask($account, $mask);
1407  $row["details"] = "<b>From</b> $from "
1408  . "<br> <b>To</b> " . getAccountDescription($dbh, $Cu, $row["to_acct"], $row["to_desc"], $row["to_accounttype"], "", $flagset3, $row["to_certnumber"]);
1409  break;
1410  case "ACHPMT":
1411  $partnerRow = $achMap[$row["id"]];
1412  $routing = trim($partnerRow["dfi_data"]["dfi_routing"]);
1413  $account = trim($partnerRow["dfi_data"]["dfi_account"]);
1414  $to = $routing . " / " . ApplyMask($account, $mask);
1415  $row["details"] = "<b>From</b> " .
1416  getAccountDescription($dbh, $Cu, $row["from_acct"], $row["from_desc"], $row["from_accounttype"], "", $flagset3, $row["from_certnumber"])
1417  . "<br> <b>To</b> $to ";
1418  break;
1419  case "TRNM2M":
1420  $extAccount = $extMap[$row["id"]];
1421  $account = trim($extAccount["remote_info"]["rdfi"]["account"]);
1422  $type = intval($extAccount["remote_info"]["rdfi"]["type"]);
1423  $type = $type == 10 ? "Checking" : "Savings";
1424  $to = ApplyMask($account, $mask) . " / $type";
1425 
1426  $row["details"] = "<b>From</b> " .
1427  getAccountDescription($dbh, $Cu, $row["from_acct"], $row["from_desc"], $row["from_accounttype"], "", $flagset3, $row["from_certnumber"])
1428  . "<br> <b>To</b> $to";
1429  break;
1430  case "TRNEXT":
1431  $extAccount = $extMap[$row["id"]];
1432  $fromLabel = $extAccount["dir"] == "from" ? "ACH From Account" : "From";
1433  $toLabel = $extAccount["dir"] == "to" ? "ACH To Account" : "To";
1434 
1435  if ($extAccount["dir"] == "from") {
1436  $routing = trim($extAccount["remote_info"]["rdfi"]["routing"]);
1437  $account = trim($extAccount["remote_info"]["rdfi"]["account"]);
1438  $from = $routing . " / " . ApplyMask($account, $mask);
1439  $to = getAccountDescription($dbh, $Cu, $row["to_acct"], $row["to_desc"], $row["to_accounttype"], "", $flagset3, $row["to_certnumber"]);
1440  } else {
1441  $from = getAccountDescription($dbh, $Cu, $row["from_acct"], $row["from_desc"], $row["from_accounttype"], "", $flagset3, $row["from_certnumber"]);
1442  $routing = trim($extAccount["remote_info"]["rdfi"]["routing"]);
1443  $account = trim($extAccount["remote_info"]["rdfi"]["account"]);
1444  $to = $routing . " / " . ApplyMask($account, $mask);
1445  }
1446  $row["details"] = "<b>From</b> $from <br> <b>To</b> $to";
1447  break;
1448  default:
1449  break;
1450  }
1451 
1452  $row["details"] .= "<br> <b>Amount</b> \$" . money_format('%.2n', $txnData["txn"]["amount"]);
1453  }
1454 
1455 
1456  unset($row["txn_data"]);
1457 
1458  $row["statusText"] = $row["status"] == "A" ? "Active" : "Inactive";
1459  $repeatingTransfers[$i] = $row;
1460  }
1461  $returnArray = array("status" => "000", "error" => "", "repeatingTransfers" => $repeatingTransfers);
1462  } catch(exception $e) {
1463  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
1464  }
1465  return $returnArray;
1466 }