PageSourceSearch

https://cdkarportal.com/js/invoice/InvoiceCtrl.js

js cdkarportal.com collected 2026-10-07 20:56:38 UTC 39,678 bytes, 1,043 lines download raw bytes

1
2angular.module('arPortal').controller('InvoiceGridCtrl',
3  [
4    '$scope',
5    '$location',
6    '$filter',
7    '$http',
8    'InvoiceSvc',
9    '$routeParams',
10    '$modal',
11    'CustomerAccountSvc',
12    'GridSvc',
13    'gridData',
14    'principal',
15    'StatementSvc',
16    'cenposConstants',
17    'PaymentSvc',
18    '$route',
19    'csvService',
20    'dealerDetailsData',
21    'portalConfigService',
22    'accessService',
23    function(
24      $scope,
25      $location,
26      $filter,
27      $http,
28      InvoiceSvc,
29      $routeParams,
30      $modal,
31      CustomerAccountSvc,
32      GridSvc,
33      gridData,
34      principal,
35      StatementSvc,
36      cenposConstants,
37      PaymentSvc,
38      $route,
39      csvService,
40      dealerDetailsData,
41      portalConfigService,
42      accessService
43    ) {
44      $scope.csvService = csvService;
45      $scope.data = {};
46      $scope.data.statementAmountTotal = 0;
47      $scope.data.showOnlinePayInfo = dealerDetailsData.allowingOnlinePayments;
48      $scope.data.showOnlineDispute = dealerDetailsData.allowCustomersToDispute;
49      $scope.detailsViewData = {};
50      $scope.statementDetails = {};
51      $scope.statementDetails.invoices = gridData; // In this controller, statementDetailsData equivalent is gridData, passed during route resolution
52      $scope.portalConfig = portalConfigService;
53
54      $scope.interactiveObj = {};
55      $scope.interactiveHeader = {};
56      $scope.detailsViewData.surchargePercent=0;
57      $scope.detailsViewData.allowSurcharge=false;
58      $scope.detailsViewData.payOptions='';
59      $scope.detailsViewData.payOne = '';
60
61        // helper formatting/type-coercion function
62      function twoDigitNum(x) {
63        return (typeof x !== 'number') ? parseFloat(x) : x;
64      }
65
66      function initStatementDetailsScope(statementDetails) {
67        var dealerAccessRole = accessService.getMostPrivilegedRole(portalConfigService.getDealerId(), principal.identity().getDealerAccessRoles());
68        var portalCustomerId = (dealerAccessRole.getPortalCustomer() && dealerAccessRole.getPortalCustomer().getId()) ? dealerAccessRole.getPortalCustomer().getId() : null;
69        $scope.paymentTransaction = {
70          totalAmount: 0.00,
71          // FIXME UUID
72          statement: {invoices: gridData, dealer: {id: portalConfigService.getDealerId()}, dmsId: $routeParams.statementDmsId, statementDate: decodeURIComponent($routeParams.statementDate), dmsSourceRecord: false},
73          dealer: {id: portalConfigService.getDealerId()},
74          // portalCustomer: {id: $scope.identity().getPortalCustomerId()},
75          portalCustomer: {id: portalCustomerId},
76          customerAccount: {id: $routeParams.customerAccountId}
77        };
78
79        $scope.detailsViewData.payOptions = sessionStorage.getItem('arpayoption');
80
81        CustomerAccountSvc.loadCustomerAccount($routeParams.customerAccountId).then(function(customer) {
82          $scope.customerAccountName = customer.name;
83          $scope.paymentTransaction.statement.customerAccount = customer;
84
85          $scope.detailsViewData.allowSurcharge=customer.dmsLogon.allowSurcharge;
86
87          $scope.interactiveHeader = {
88            'stmtDate': decodeURIComponent($routeParams.statementDate),
89            'custNo': customer.controlNumber,
90            'userId': principal.identity().getId(),
91            'dmsId': $scope.paymentTransaction.statement.dmsId
92          };
93
94          $scope.interactiveObj = InvoiceSvc.processInteractiveState($scope.interactiveHeader, $scope.statementDetails.invoices, 'get');
95
96          $scope.statementDetails.invoices = angular.copy($scope.interactiveObj); // update the saved preferences to statementDetails.invoices
97
98          $scope.gridData = $scope.statementDetails.invoices;
99
100          updateTotalAmount();
101
102          if (InvoiceSvc.hasInteractive) {
103            $scope.clearSelections(true);
104          }
105        });
106      }
107
108      if (typeof gridData != 'undefined' && gridData.length > 0) {
109        angular.forEach(gridData, function(item) {
110          $scope.data.statementAmountTotal += item.statementAmount;
111        });
112      }
113
114      $scope.modalData = {};
115
116      $scope.fetchCsv = function() {
117        InvoiceSvc.fetchCsv($routeParams.customerAccountId);
118      };
119
120      $scope.setPDFURL = function(invoice) {
121        InvoiceSvc.fetchPDFWithAvailabilityCheck(invoice, $routeParams.customerAccountId);
122      };
123
124      var invoiceNumberCellTemplate;
125      var canViewInvoicePdf = principal.identity().hasRightToCustAcct('VIEW_INVOICE_PDF', $routeParams.customerAccountId);
126
127      if (canViewInvoicePdf) {
128        invoiceNumberCellTemplate = '<div class="ngCellText"><a href="javascript:void(0);" data-ng-click="showCMPopUp(row.entity)">{{row.getProperty(col.field)}}</a></div>';
129      } else {
130        invoiceNumberCellTemplate = '<div class="ngCellText">{{row.getProperty(col.field)}}</div>';
131      }
132
133      $scope.setCMPDFURL = function(invoice, dsdaRefer, invoiceAccount) {
134        var InvoiceCopy = angular.copy(invoice);
135        InvoiceCopy.invoiceNumber = dsdaRefer;
136        InvoiceCopy.invoiceAccount = invoiceAccount;
137        InvoiceSvc.fetchPDFWithAvailabilityCheck(InvoiceCopy, $routeParams.customerAccountId);
138      };
139
140      $scope.showCMPopUp = function(entity) {
141        if (entity.dsdaRefers.length == 0) {
142          $scope.setCMPDFURL(entity, entity.invoiceNumber, entity.invoiceAccount);
143        } else if (entity.dsdaRefers.length > 1) {
144          $scope.askForCreditMemo(entity);
145        } else {
146          $scope.setCMPDFURL(entity, entity.dsdaRefers[0].dsdaRefer, entity.dsdaRefers[0].logon);
147        }
148      };
149
150        // pops up creditMemo modal,sets entity values accordingly
151      $scope.askForCreditMemo = function(entity) {
152        $scope.modalData.headingText = 'Display Invoice or Credit Memo';
153        $scope.modalData.entity = angular.copy(entity);
154        var modalInstance = $modal.open({
155          templateUrl: 'creditMemoModal.html',
156          windowClass: 'cm-modal-window',
157          scope: $scope
158        });
159      };
160
161      $scope.clearSelections = function(current) {
162        $scope.modalData.headingText = (current) ? 'Previous Statement Selections Found' : 'Clear All Previous Statement Selections';
163        $scope.modalData.confirmValue = '';
164        var modalInstance = $modal.open({
165          templateUrl: 'clearSelectionsModal.html',
166          windowClass: 'cm-modal-window',
167          scope: $scope
168        });
169        modalInstance.result.then(function(data) {
170          if (data.confirmValue == 'YES') {
171            if (current) {
172              InvoiceSvc.completedInteractionState();
173            } else {
174              InvoiceSvc.clearLocalStorage();
175            }
176            InvoiceSvc.hasInteractive = false;
177            $route.reload();
178          }
179        }, function() {// dismissed modal
180                // do nothing
181        });
182      };
183
184      initStatementDetailsScope($scope.statementDetails);// , $scope.paymentTransaction.statement);
185
186        // Moved into initStatementDetailsScope
187        // updateTotalAmount();
188
189//        if(InvoiceSvc.hasInteractive) {
190//            $scope.clearSelections(true);
191//        }
192
193      function canDispute() {
194        if (!$scope.detailsViewData.cantPay) return false;
195        for (var i = 0; i < gridData.length; i++) {
196          if (!gridData[i].previouslyDisputed && gridData[i].disputed) return true;
197        }
198        return false;
199      }
200
201        /**
202         * @description: Determines if credit or payment amount is valid for
203         * this invoice. If invalid, it will reset the value to the invoice
204         * balance. If valid, it will continue on to 'askForCommentAndUpdate'.
205         * #This is a comment
206         * # Another comment
207         *
208         * @function: $scope.validatePaymentBounds
209         * @param: {object} entity - object representing row of ng-grid data.
210         */
211      $scope.validatePaymentBounds = function(entity) {
212        var pAmt = twoDigitNum(entity.currentPaidAmount),
213          sAmt = twoDigitNum(entity.statementAmount),
214          iBal = twoDigitNum(entity.invoiceBalance),
215          paid = entity.paid,
216          isCredit = false,
217          isPayment = false;
218
219            /*
220             console.log('entity:');
221             console.log(entity);
222             console.log('pAmt:');
223             console.log(pAmt);
224             console.log('sAmt:');
225             console.log(sAmt);
226             console.log('iBal:');
227             console.log(iBal);
228             console.log('isPaid:');
229             console.log(entity.isPaid);
230             console.log('paid:');
231             console.log(entity.paid);
232             */
233
234        isCredit = ((iBal <= pAmt) && (pAmt < 0));
235            // isPayment   = ((0 <= pAmt) && (pAmt <= iBal));
236        isPayment = ((0 <= pAmt) && (0 <= iBal));
237
238        if (paid && (isCredit || isPayment)) {
239                // console.log('valid');
240          $scope.askForCommentAndUpdate(entity);
241        } else {
242                // console.log('invalid');
243          entity.currentPaidAmount = (paid) ? iBal : 0.00;
244        }
245      };
246
247        // pops up paymentComment modal,sets entity values accordingly
248      $scope.askForCommentAndUpdate = function(entity) {
249        var currentPaidAmount = twoDigitNum(entity.currentPaidAmount),
250          statementAmount = twoDigitNum(entity.statementAmount),
251          invoiceBalance = twoDigitNum(entity.invoiceBalance),
252          modalInstance = null;
253
254        if (entity.paid && currentPaidAmount !== invoiceBalance) {
255          $scope.modalData.headingText = 'Reason for amount change';
256          $scope.modalData.placeholdVal = 'Please provide reason for change in amount being paid';
257          $scope.modalData.invoiceComment = entity.paymentComment ? entity.paymentComment : '';
258          $scope.notes = {};
259          $scope.notes.maxLength = 132;
260
261          modalInstance = $modal.open({
262            templateUrl: 'invoiceCommentModal.html',
263            scope: $scope
264          });
265
266                // result promise
267          modalInstance.result.then(
268                    function(invoiceComment) {
269                      entity.paymentComment = invoiceComment;
270                      InteractiveState('currentPaidAmount', entity.currentPaidAmount, entity);
271                      updateTotalAmount();
272                    },
273                    function() {
274                        // dismissed paymentComment modal, reset to statementAmount unless comment is already present;
275                      if (!entity.paymentComment) {
276                        entity.currentPaidAmount = entity.statementAmount;
277                        InteractiveState('currentPaidAmount', entity.currentPaidAmount, entity);
278                        updateTotalAmount();
279                      }
280                    });
281        } else {
282          entity.paymentComment = null;
283          updateTotalAmount();
284        }
285      };
286
287      $scope.disputeIt = function(entity) {
288        if (entity.disputed) {
289          $scope.modalData.headingText = 'Reason for dispute';
290          $scope.modalData.placeholdVal = 'Please provide reason for disputing this invoice';
291          $scope.modalData.invoiceComment = '';
292          $scope.notes = {};
293          $scope.notes.maxLength = 132;
294
295          var modalInstance = $modal.open({
296            templateUrl: 'invoiceCommentModal.html',
297            scope: $scope
298          });
299
300                // result promise
301          modalInstance.result.then(function(invoiceComment) { // entered comment
302            entity.disputedComment = invoiceComment;
303            if (entity.paid) {
304              entity.currentPaidAmount = 0.00;
305              entity.paid = false;
306              entity.paymentComment = null;
307              updateTotalAmount();
308            }
309            InteractiveState('disputed', true, entity);
310          }, function() {// dismissed disputeComment modal, reset to false;
311            entity.disputed = false;
312            InteractiveState('disputed', false, entity);
313          });
314        } else { // unchecked disputed,
315          entity.disputedComment = null;
316          InteractiveState('disputed', false, entity);
317        }
318      };
319
320      var InteractiveState = function(field, value, invoice) {
321// console.log(">>>>>>Interactive State");
322// console.log(">>>>>>>>>>>>length: " + $scope.interactiveObj.length);
323        for (var i = 0; i < $scope.interactiveObj.length; i++) {
324          if ($scope.interactiveObj[i].invoiceNumber == invoice.invoiceNumber) {
325            $scope.interactiveObj[i].disputedComment = invoice.disputedComment;
326            $scope.interactiveObj[i].paymentComment = invoice.paymentComment;
327
328            if (field == 'paid') {
329              $scope.interactiveObj[i].paid = value;
330              if (value) {
331                $scope.interactiveObj[i].disputed = false;
332                $scope.interactiveObj[i].currentPaidAmount = twoDigitNum(invoice.statementAmount);
333              } else {
334                $scope.interactiveObj[i].currentPaidAmount = 0.00;
335              }
336            } else if (field == 'disputed') {
337              $scope.interactiveObj[i].disputed = value;
338              if (value) {
339                $scope.interactiveObj[i].paid = false;
340                $scope.interactiveObj[i].currentPaidAmount = 0.00;
341              }
342            } else if (field == 'currentPaidAmount') {
343              $scope.interactiveObj[i].currentPaidAmount = value;
344            }
345          }
346        }
347        InvoiceSvc.processInteractiveState($scope.interactiveHeader, $scope.interactiveObj, 'set');
348      };
349
350      $scope.payIt = function(entity) {
351        InteractiveState('paid', entity.paid, entity);
352        if (entity.paid) {
353          entity.disputed = false;
354          entity.disputedComment = null;
355          entity.currentPaidAmount = twoDigitNum(entity.statementAmount);
356        } else {
357          entity.paymentComment = null;
358          entity.currentPaidAmount = 0.00;
359          $scope.detailsViewData.allSelected = false;
360        }
361        if (!$scope.detailsViewData.payOne && $scope.detailsViewData.payOptions == "both" && dealerDetailsData.surcharge > 0) {
362          $scope.detailsViewData.payOne = "cards";
363        }
364        updateTotalAmount();
365      };
366
367      $scope.updateSurchargeIfBothAllowed = function(){
368        updateTotalAmount();
369      };
370
371      function updateTotalAmount() {
372        var total = twoDigitNum(0.00),
373          i = 0;
374
375        for (i = 0; i < $scope.statementDetails.invoices.length; i++) {
376          total += twoDigitNum($scope.statementDetails.invoices[i].currentPaidAmount);
377        }
378        $scope.paymentTransaction.surcharge = 0; //default amt for non card payment types or no surcharge
379        $scope.detailsViewData.surchargePercent = 0; //if 0 do not display surcharge (because not credit card or not setup for surcharge)
380        if (dealerDetailsData.allowingOnlinePayments){
381
382          if ($scope.detailsViewData.allowSurcharge){
383            if (dealerDetailsData.surcharge > 0 && ($scope.detailsViewData.payOne == "cards" || $scope.detailsViewData.payOptions == "cards")){
384              $scope.detailsViewData.surchargePercent = twoDigitNum(dealerDetailsData.surcharge);
385              $scope.paymentTransaction.surcharge = Math.abs((total * $scope.detailsViewData.surchargePercent / 100));
386
387              $scope.paymentTransaction.surcharge = $scope.paymentTransaction.surcharge.toFixed(2);
388              total += twoDigitNum($scope.paymentTransaction.surcharge);
389            }             
390          }else{
391            $scope.paymentTransaction.surcharge = "notsetup";
392          }
393   
394        }
395        $scope.paymentTransaction.totalAmount = twoDigitNum(total);
396        $scope.myCsvRender($scope.data.statementAmountTotal, $scope.paymentTransaction.totalAmount);
397      }
398
399      $scope.disputeStatementItems = function() {
400        InvoiceSvc.disputeStatementItems($scope.paymentTransaction.statement)
401                .then(success, failure);
402        function success(statement) {
403          InvoiceSvc.completedInteractionState();
404          $location.path($scope.identity().getDashBoardPath());
405        }
406        function failure() {
407          bootbox.arAlert('Problem encountered disputing statement items');
408        }
409      };
410
411      $scope.payStatement = function() {
412        if ($scope.detailsViewData.payOptions == "both" && dealerDetailsData.surcharge > 0){
413          //do not allow both, so they cannot switch check to card or back to avoid surcharges
414          sessionStorage.setItem('arpayoption', $scope.detailsViewData.payOne); 
415        }
416        function success(rslt) {if (rslt)$location.path(cenposConstants.paymentPath);} // transaction started successfully,load iframe
417        function failure(reason) { // error starting transaction
418          bootbox.arAlert(reason);
419          $route.reload();
420        }
421
422        if ($scope.paymentTransaction.totalAmount >= 1.00) {
423          var rslt = PaymentSvc.startPortalTransactionSynch($scope.paymentTransaction);
424          if (rslt.success) {
425            $location.path(cenposConstants.paymentPath + '/invoices');
426          } else {
427            bootbox.arAlert(rslt.msg);
428          }
429        } else {
430          bootbox.arAlert('Payment total must be greater than or equal to $1.00');
431        }
432      };
433
434        // fired off by checking pay all box or when filterOptions change(in this case, uncheck all from full list)
435      $scope.togglePayAll = function(allSelected) {
436        var applyOnFilteredData = (allSelected && $scope.filterOptions.filterText);
437            // var statementDetails = (applyOnFilteredData)?$scope.gridOptions.ngGrid.filteredRows:$scope.statementDetails;
438        var statementDetails = (applyOnFilteredData) ? $scope.myData : $scope.statementDetails;
439        for (var i = 0; i < statementDetails.invoices.length; i++) {
440          if (statementDetails.invoices[i].statementAmount === 0) {
441                    // Only select invoices with a statement amount to pay
442            continue;
443          }
444
445          for (var j = 0; j < $scope.interactiveObj.length; j++) {
446            if ($scope.interactiveObj[j].invoiceNumber == statementDetails.invoices[i].invoiceNumber) {
447              $scope.interactiveObj[j].paid = allSelected;
448              InteractiveState('paid', allSelected, $scope.interactiveObj[j]);
449            }
450          }
451
452          var invoice = statementDetails.invoices[i];
453          setPaidCheckBox(invoice);
454        }
455        function setPaidCheckBox(invoice) {
456          invoice.paid = allSelected;
457          $scope.payIt(invoice);
458        }
459      };
460
461        // WATCHES
462      $scope.detailsViewData.cantPay = true;
463      $scope.$watch(
464            'paymentTransaction.totalAmount',
465            function(newVal) {
466              $scope.detailsViewData.cantPay = (newVal <= 0.00);
467            },
468            true
469        );
470
471      $scope.data.cantDisputeStatementItems = true;
472      $scope.$watch(
473            'paymentTransaction.statement.invoices',
474//            'gridData',
475            function() {
476              $scope.detailsViewData.cantDisputeStatementItems = !canDispute();
477            },
478            true
479        );
480
481        // uncheck payAll options if already checked. disable payAll if filter text != ""
482      $scope.$watch('filterOptions.filterText', function(newVal) {
483        if ($scope.detailsViewData.allSelected) {
484          $scope.togglePayAll(false);
485        }
486        $scope.payAllDisabled = (newVal);
487      }, true);
488
489      var disputeCheckBoxTemplate =
490            '<div class="ngCellText" ng-switch on="row.entity.disputeDisplay">' +
491            '<div ng-switch-when="blank"><small style="margin-bottom:10px">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</small></div>' +
492            '<div ng-switch-when="label"><small style="margin-bottom:10px" class="label important">Disputed</small></div>' +
493            '<div ng-switch-when="checkbox">' +
494            '<input style="margin-top:4px;" type="checkbox" ng-model="row.entity.disputed" ng-change="disputeIt(row.entity)" />' +
495            '<small style="margin-bottom:10px"> Dispute</small></div>' +
496            '</div>';
497
498      var payCheckBoxTemplate =
499            '<div class="ngCellText"> ' +
500            '<input style="margin-top:4px;" type="checkbox" ' +
501            'data-ng-hide="row.entity.isPaid" ng-model="row.entity.paid" ng-change="payIt(row.entity)" /> ' +
502            '<small ng-hide="row.entity.isPaid" >Pay</small>' +
503            '<small ng-hide="!row.entity.isPaid" class="label success">Paid</small>' +
504            '</div> ';
505
506      var editableAmountTemplate =
507            '<div class="input-prepend"> ' +
508            '<span class="add-on">$</span>' +
509            '<input ' +
510            'data-ui-blur="validatePaymentBounds(row.entity)" ' +
511            'data-ng-readonly="(row.entity.currentPaidAmount == 0 && row.entity.isPaid) || (!row.entity.isPaid && !row.entity.paid)" ' +
512            'ng-class="\'colt\' + col.index" ' +
513            'data-ng-input="COL_FIELD" ' +
514            'ng-pattern="/^\\-?(\\d+\\.?\\d{0,2}|\\.\\d{1,2})$/" ' +
515            'ng-model="COL_FIELD" ' +
516            '/>' +
517            '</div>';
518
519      var checkboxHeaderTemplate =
520            '<div class="ngCellText">' +
521            '<input style="margin-top:4px;" ' +
522            'type="checkbox" ' +
523            'data-ng-disabled="payAllDisabled" ' +
524            'ng-model="detailsViewData.allSelected" ' +
525            'ng-change="togglePayAll(detailsViewData.allSelected)"/>' +
526            '<span> PayAll</span>' +
527            '</div>';
528
529      var paidAmountCellTemplate = '<div class="ngCellText" style="text-align:right;">{{row.getProperty(col.field) | currency}}</div>'; // Amount to Pay
530      var amountCellTemplate = '<div class="ngCellText">{{row.getProperty(col.field) | currency}}</div>';
531
532      var columnDefs = [
533        {
534          field: 'invoiceDate',
535          cellFilter: 'date:"MM/dd/yyyy"',
536          enableCellEdit: false,
537          displayName: 'Date',
538          headerClass: 'grid-header-center'
539        },
540        {
541          field: 'invoiceNumber',
542          enableCellEdit: false,
543          displayName: 'Invoice Number',
544          headerClass: 'grid-header-center',
545          cellTemplate: invoiceNumberCellTemplate
546        },
547        {
548          field: 'dealershipName',
549          width: '30%',
550          enableCellEdit: false,
551          displayName: 'Dealership Name',
552          headerClass: 'grid-header-center'
553        },
554        {
555          field: 'amount',
556          enableCellEdit: false,
557          displayName: 'Purchases',
558          headerClass: 'grid-header-center',
559          cellFilter: 'currency'
560        },
561        {
562          field: 'paidAmount',
563          enableCellEdit: false,
564          displayName: 'Payments /<br/>Credits',
565          headerClass: 'grid-header-center',
566          cellFilter: 'currency'
567        },
568        {
569          field: 'statementAmount',
570          enableCellEdit: false,
571          displayName: 'Statement<br/>Amount',
572          headerClass: 'grid-header-center',
573          cellFilter: 'currency'
574        }];
575
576            // Add in fields for Online Payments info if allowed
577      if ($scope.data.showOnlinePayInfo) {
578        columnDefs.push({
579          field: 'portalPaidAmount',
580          enableCellEdit: false,
581          displayName: 'Recent Portal<br>Payment',
582          headerClass: 'grid-header-center',
583          cellTemplate: amountCellTemplate,
584          cellFilter: 'currency'
585        });
586
587        columnDefs.push({
588          field: 'invoiceBalance',
589          enableCellEdit: false,
590          displayName: 'Invoice<br>Balance',
591          headerClass: 'grid-header-center',
592          cellTemplate: amountCellTemplate,
593          cellFilter: 'currency'
594        });
595      }
596      if ($scope.data.showOnlineDispute) {
597          columnDefs.push({
598            field: 'disputed',
599            width: '70px',
600            enableCellEdit: false,
601            displayName: 'Dispute',
602            headerClass: 'grid-header-center',
603            cellTemplate: disputeCheckBoxTemplate
604          });
605      }
606
607      if ($scope.data.showOnlinePayInfo) {
608        columnDefs.push({
609          field: 'paid',
610          width: '70px',
611          enableCellEdit: false,
612          displayName: 'Pay All',
613          cellTemplate: payCheckBoxTemplate,
614          headerCellTemplate: checkboxHeaderTemplate
615        });
616
617        columnDefs.push({
618          field: 'currentPaidAmount',
619          width: '100px',
620          enableCellEdit: true,
621          displayName: 'Amount<br>To Pay',
622          headerClass: 'grid-header-center',
623          cellTemplate: paidAmountCellTemplate,
624          editableCellTemplate: editableAmountTemplate,
625          cellFilter: 'currency'
626        });
627      }
628
629      $scope.gridOptions = GridSvc.config($scope, gridData, columnDefs)
630          .withOpts({headerRowHeight: 50})
631          .withPaging(false)
632          .build();
633      GridSvc.initWatches($scope);
634
635        // ===============================================
636        // CIARP-130
637        // Begin code to support client-side csv
638
639      var renderCsv = csvService.getRender(columnDefs, ['disputed', 'paid', 'currentPaidAmount']);
640      $scope.myCsvRender = function myCsvRender(statementAmountTotal, totalAmount) {
641        var totalsLine = {
642          cells: [
643              ['string', 'Total Balance:'],
644              // ['currency', $scope.data.statementAmountTotal]
645              ['currency', statementAmountTotal]
646          ],
647          align: 'right'
648        };
649          // var payAmountLine = {
650          //   cells: [
651          //     ['string', 'Pay This Amount:'],
652          //     // ['currency', $scope.paymentTransaction.totalAmount]
653          //     ['currency', totalAmount]
654          //   ],
655          //   align: 'right'
656          // };
657          // var customLines = [totalsLine, payAmountLine];
658        var customLines = [totalsLine];
659        renderCsv(gridData, customLines);
660      };
661
662        // End code to support client-side csv
663        // ===============================================
664    }]);
665
666angular.module('arPortal').controller('InvoiceSearchCtrl',
667    ['$scope', '$routeParams', 'principal', 'GridSvc', 'InvoiceSvc','CustomerSvc', 'CustomerAccountSvc', '$timeout', 'csvService', 'accessService', 'portalConfigService', 'Utils',
668    function($scope, $routeParams, principal, GridSvc, InvoiceSvc, CustomerSvc, CustomerAccountSvc, $timeout, csvService, accessService, portalConfigService, Utils) {
669      // var thePortalConfig = portalConfig.getPortalConfig();
670      var invoiceNumberCellTemplate = '';
671      var columnDefs = [];
672
673      $scope.csvService = csvService;
674      $scope.grid = {};
675      $scope.grid.visible = false;
676
677      // var sourceCellTemplate = '<div class="ngCellText"><span ng-show="{{row.entity.openArInvoice}}">Open AR</span>' +
678      //   '<span ng-show="{{!row.entity.openArInvoice}}">Statement</span></div>';
679
680      invoiceNumberCellTemplate =
681          '<div class="ngCellText">' +
682          '<a href="javascript:void(0);" ' +
683          'data-ng-click="showCMPopUp(row.entity)" ' +
684          'data-ng-show="{{canViewInvoicePdf}}">' +
685          '{{row.getProperty(col.field)}}' +
686          '</a>' +
687          '<span data-ng-show="{{!canViewInvoicePdf}}">' +
688          '{{row.getProperty(col.field)}}' +
689          '</span>' +
690          '</div>';
691
692      columnDefs = [
693        {
694          field: 'invoiceDate',
695          displayName: 'Date',
696          cellFilter: 'date:"MM/dd/yyyy"',
697          enableCellEdit: false,
698          headerClass: 'grid-header-center'
699        },
700        {
701          field: 'dsdaType',
702          displayName: 'Type',
703          enableCellEdit: false,
704          headerClass: 'grid-header-center'
705        },
706        {
707          field: 'translatedOpenArInvoice',
708          displayName: 'Source',
709          enableCellEdit: false,
710          headerClass: 'grid-header-center'//,
711          // cellTemplate: sourceCellTemplate,
712        },
713        {
714          field: 'dsdaRefer',
715          displayName: 'Invoice #',
716          enableCellEdit: false,
717          headerClass: 'grid-header-center',
718          cellTemplate: invoiceNumberCellTemplate
719        },
720        {
721          field: 'amount',
722          displayName: 'Amount',
723          enableCellEdit: false,
724          headerClass: 'grid-header-center',
725          cellFilter: 'currency'
726        },
727        {
728          field: 'dealershipName',
729          displayName: 'Dealership',
730          enableCellEdit: false,
731          headerClass: 'grid-header-center'
732        }
733      ];
734
735      function getEmptySearchParameters() {
736        return {
737          portalCustomerId: null,
738          customerAccountId: null,
739          customer: null,
740          fromDate: null,
741          toDate: null,
742          invoiceTypes: [],
743          invoiceAmt: null,
744          poNo: null,
745          vehicleNo: null,
746          invoiceNos: ''
747        };
748      }
749
750      $scope.gridData = [];
751      $scope.invoiceTypeList = [
752            // {'lookupCode': 'S*P*L*M*F*R*C', 'description': 'All Invoice Types'},
753            {'lookupCode': 'F', 'description': 'Fuel Invoice', 'isChecked': false},
754            {'lookupCode': 'L', 'description': 'Lease Invoice', 'isChecked': false},
755            {'lookupCode': 'M', 'description': 'Misc Invoice', 'isChecked': false},
756            {'lookupCode': 'P', 'description': 'Parts Invoice', 'isChecked': false},
757            {'lookupCode': 'R', 'description': 'Rental Invoice', 'isChecked': false},
758            {'lookupCode': 'S', 'description': 'Service Invoice', 'isChecked': false},
759            {'lookupCode': 'C', 'description': 'Truck Invoice', 'isChecked': false}
760      ];
761
762      $scope.$watch('data.checkAllInvoiceTypes', function(value) {
763        for (var i = 0; i < $scope.invoiceTypeList.length; i++) {
764          $scope.invoiceTypeList[i].isChecked = value;
765        }
766      });
767
768      $scope.displayInvoiceTypes = [];
769
770      for (var i = 0; i < $scope.invoiceTypeList.length; i++) {
771        if ((i % 3) === 0) $scope.displayInvoiceTypes.push([]);
772        $scope.displayInvoiceTypes[$scope.displayInvoiceTypes.length - 1].push($scope.invoiceTypeList[i]);
773      }
774
775      $scope.refreshSelectAll = function() {
776        var totalSelected = 0,
777          totalUnselected = 0;
778
779        angular.forEach($scope.invoiceTypeList, function(item) {
780          if (item.isChecked) {
781            totalSelected++;
782          } else {
783            totalUnselected++;
784          }
785          if ($scope.invoiceTypeList.length == totalSelected) {
786            $scope.data.checkAllInvoiceTypes = true;
787          }
788          if ($scope.invoiceTypeList.length == totalUnselected) {
789            $scope.data.checkAllInvoiceTypes = false;
790          }
791        });
792      };
793
794      $scope.portalCustomerList = [];
795      $scope.customerAccountList = [];
796      $scope.formAccountList = [];
797
798      $scope.data = {
799        searchParameters: getEmptySearchParameters()
800      };
801
802      // FIXME
803      var dealerAccessRole = accessService.getMostPrivilegedRole(portalConfigService.getDealerId(), principal.identity().getDealerAccessRoles());
804
805      CustomerAccountSvc.loadCustomerAccountsForCU(Utils.helpGetDealerId()).then(
806        function success (customerAccountsData) {
807          angular.forEach(customerAccountsData, function(account) {
808            $scope.customerAccountList.push({
809              'id': account.id,
810              'name': account.name,
811              'controlNumber': account.controlNumber,
812              'nameAndControlNumber': account.name + ' (' + account.controlNumber + ')',
813              'portalCustomerId': account.portalCustomerId
814            });
815          });
816
817          CustomerSvc.loadCustomers(Utils.helpGetDealerId()).then(
818            function success (portalCustomerData) {
819              var hasAccounts;
820              angular.forEach(portalCustomerData, function(customer) {
821                hasAccounts = false;
822                angular.forEach($scope.customerAccountList, function(account) {
823                  if(account.portalCustomerId == customer.id){
824                      hasAccounts = true;
825                    }
826                });
827                if(hasAccounts){
828                  $scope.portalCustomerList.push({
829                    'id': customer.id,
830                    'name': customer.name
831                  });
832                }
833              });
834
835              $scope.portalCustomerList.sort(function(a, b) {
836                var n1 = a.name.trim(),
837                n2 = b.name.trim();
838                if (n1 === n2) {
839                  return 0;
840                }
841                return (n1 < n2) ? -1 : 1;
842              });
843
844              $scope.loadFormAccounts($scope.portalCustomerList[0].id);
845
846            },
847            function failure (result) {
848              console.error(result);
849              bootbox.arAlert('Failed to load customers.')
850            }
851          );
852
853        },
854        function failure (result) {
855          console.error(result);
856          bootbox.arAlert('Failed to load customer accounts.')
857        }
858      )
859      .then(
860        function success () {
861          $scope.canViewInvoicePdf = principal
862            .identity()
863            .hasRightToCustAcct(
864              'VIEW_INVOICE_PDF',
865              $scope.data.searchParameters.customerAccountId
866            );
867        },
868        function failure () {
869          // pass
870        }
871      );
872
873      $scope.loadFormAccounts = function(portalCustomerId){
874        // console.log("loadFormAccounts - portalCustomerId = " + portalCustomerId);
875        $scope.formAccountList = [];
876        angular.forEach($scope.customerAccountList, function(customerAccount) {
877          if(customerAccount.portalCustomerId == portalCustomerId){
878            $scope.formAccountList.push(customerAccount);
879          }
880        });
881        $scope.formAccountList.sort(function(a, b) {
882          var n1 = a.nameAndControlNumber.trim(),
883              n2 = b.nameAndControlNumber.trim();
884          if (n1 === n2) {
885            return 0;
886          }
887          return (n1 < n2) ? -1 : 1;
888        });
889        // console.dir($scope.formAccountList);
890
891        $scope.data.searchParameters.portalCustomerId = portalCustomerId;
892        $scope.data.searchParameters.customerAccountId = $scope.formAccountList[0].id;
893      };
894
895
896      $scope.setCMPDFURL = function(invoice, dsdaRefer, invoiceAccount) {
897        var InvoiceCopy = angular.copy(invoice);
898        InvoiceCopy.invoiceNumber = dsdaRefer;
899        InvoiceCopy.invoiceAccount = invoiceAccount;
900        InvoiceSvc.fetchPDFWithAvailabilityCheck(InvoiceCopy, $scope.data.searchParameters.customerAccountId);
901      };
902
903        // pops up creditMemo modal, sets entity values accordingly
904      $scope.askForCreditMemo = function(entity) {
905        $scope.modalData.headingText = 'Display Invoice or Credit Memo';
906        $scope.modalData.entity = angular.copy(entity);
907        var modalInstance = $modal.open({
908          templateUrl: 'creditMemoModal.html',
909          windowClass: 'cm-modal-window',
910          scope: $scope
911        });
912      };
913
914      $scope.showCMPopUp = function(entity) {
915//            console.log('entity:');
916//            console.dir(entity);
917
918        if (entity.dsdaRefers.length == 0) {
919          $scope.setCMPDFURL(entity, entity.invoiceNumber, entity.invoiceAccount);
920        } else if (entity.dsdaRefers.length > 1) {
921          $scope.askForCreditMemo(entity);
922        } else {
923          $scope.setCMPDFURL(entity, entity.dsdaRefers[0].dsdaRefer, entity.dsdaRefers[0].logon);
924        }
925      };
926
927      $scope.gridOptions = GridSvc.config($scope, $scope.gridData, columnDefs)
928            // .withDefaults()
929            .withPaging(true)
930            .withPagingOptions({pageSizes: [25, 50, 100, 200], pageSize: 50, currentPage: 1})
931            .build();
932      GridSvc.initWatches($scope);
933
934//        console.log('gridOptions:');
935//        console.dir($scope.gridOptions);
936
937      $scope.searchInvoices = function() {
938        if ($scope.data.searchParameters.fromDate && !$scope.data.searchParameters.toDate) {
939          bootbox.arAlert('Please enter To Date.');
940          return;
941        }
942
943        if (!$scope.data.searchParameters.fromDate && $scope.data.searchParameters.toDate) {
944          bootbox.arAlert('Please enter From Date.');
945          return;
946        }
947
948        $scope.data.searchParameters.invoiceTypes = [];
949        angular.forEach($scope.invoiceTypeList, function(item) {
950          if (item.isChecked) $scope.data.searchParameters.invoiceTypes.push(item.lookupCode);
951        });
952
953        $scope.loading = true;
954        InvoiceSvc.invoiceSearch($scope.data.searchParameters).then(function(invoices) {
955                // console.log('invoices:');
956                // console.dir(invoices);
957
958          if (invoices === '') {
959            console.log('Error: InvoiceSvc.invoiceSearch returned nothing.');
960          } else {
961            $scope.canViewInvoicePdf = principal.identity().hasRightToCustAcct('VIEW_INVOICE_PDF', $scope.data.searchParameters.customerAccountId);
962            if (!$scope.canViewInvoicePdf) {
963              $scope.canViewInvoicePdf = false;
964            }
965
966            $timeout(function() {
967              $scope.gridData = [];
968              GridSvc.initWatches($scope);
969              $scope.redrawNgGrid();
970
971              $timeout(function() {
972                $scope.gridData = angular.copy(invoices.invoices);
973                $scope.gridData.forEach(function translateOpenArInvoice(v) {
974                  v.translatedOpenArInvoice = (v.openArInvoice) ? 'Open AR' : 'Statement';
975                });
976                $scope.grid.visible = true;
977                GridSvc.initWatches($scope);
978                $scope.redrawNgGrid();
979
980                            // CIARP-130
981                            // FIXME
982                $timeout(function() {
983                  $scope.renderCsv($scope.gridData);
984                }, 500);
985              }, 100);
986            }, 100);
987          }
988          $scope.loading = false;
989        });
990      };
991
992      $scope.resetSearchInvoices = function() {
993            // console.log('reset');
994            // $scope.data.searchParameters.toDate = new Date();
995            // $scope.data.searchParameters.fromDate = new Date();
996
997        $scope.data.searchParameters = {
998          customerAccountId: $scope.customerAccountList[0].id,
999          customer: null,
1000          fromDate: new Date(),
1001          toDate: new Date(),
1002          invoiceTypes: [],
1003          invoiceAmt: null,
1004          poNo: null,
1005          vehicleNo: null,
1006          invoiceNos: ''
1007        };
1008        $scope.data.checkAllInvoiceTypes = false;
1009        for (var i = 0; i < $scope.invoiceTypeList.length; i++) {
1010          $scope.invoiceTypeList[i].isChecked = false;
1011        }
1012
1013        $scope.gridData = [];
1014        $scope.grid.visible = false;
1015        GridSvc.initWatches($scope);
1016        $scope.redrawNgGrid();
1017
1018        $timeout(function() {
1019          $scope.data.searchParameters.fromDate = null;
1020          $scope.data.searchParameters.toDate = null;
1021        }, 100);
1022      };
1023
1024      $scope.redrawNgGrid = function() {
1025            // HACK -- Call resize window events to force redraw of ng-grid after reloading new search data
1026            // https://github.com/angular-ui/ng-grid/issues/50
1027        window.setTimeout(function() {
1028          $(window).resize();
1029          $(window).resize();
1030                // console.log('bonus round: ' + csvService.csv.isReady);
1031        }, 1);
1032      };
1033
1034      $scope.fetchInvoiceSearchCsv = function() {
1035        InvoiceSvc.fetchInvoiceSearchCsv($scope.data.searchParameters);
1036      };
1037
1038        // CIARP-130
1039        // Begin code to support client-side csv
1040      $scope.renderCsv = csvService.getRender(columnDefs);
1041      $scope.renderCsv($scope.gridData);
1042        // End code to support client-side csv
1043    }]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.