| @@ -43,3 +43,56 @@ | |||
| .upload-details div[role="tabpanel"]{ | |||
| padding:0px; | |||
| } | |||
| @keyframes flash-background { | |||
| from {background-color: white;} | |||
| to {background-color: lightgoldenrodyellow;} | |||
| } | |||
| kendo-grid.filterByUploadMeta { | |||
| .k-grid-add-row.k-grid-edit-row.ng-star-inserted { | |||
| animation: flash-background 1s infinite; | |||
| opacity: 1; | |||
| } | |||
| .k-state-selected { | |||
| animation: flash-background 1s 3; | |||
| opacity: 1; | |||
| } | |||
| tr { | |||
| position: relative; | |||
| } | |||
| @keyframes flash{ | |||
| 50% { opacity: 0.5 } | |||
| } | |||
| tr.dup { | |||
| td:first-child{ | |||
| position: relative; | |||
| div { | |||
| display:unset; | |||
| transition: display 1s; | |||
| z-index:1; | |||
| } | |||
| } | |||
| } | |||
| tr.missingLoan { | |||
| td:last-child { | |||
| text-align: left; | |||
| &::before{ | |||
| color: #020202; | |||
| background: #ffe172; | |||
| width: 110px; | |||
| content: "missing loan"; | |||
| text-align: right; | |||
| position: absolute; | |||
| overflow:hidden; | |||
| right: 0; | |||
| border-radius: 10px 0 0 10px; | |||
| animation: flash 1s 3; | |||
| } | |||
| }; | |||
| } | |||
| } | |||
| @@ -62,11 +62,21 @@ export class LoanSelectComponent implements OnInit, ControlValueAccessor { | |||
| } | |||
| onLoanSelected(loan: LoanModel): void { | |||
| if (loan !== undefined && this.Loan !== undefined && this.Loan.Id !== loan.Id){ | |||
| let changed = false; | |||
| if ( loan === null || loan === undefined) { | |||
| if (this.Loan.Id !== '') { | |||
| changed = true; | |||
| } | |||
| }else{ | |||
| changed = this.Loan.Id !== loan.Id; | |||
| } | |||
| if (changed ){ | |||
| this.valueChange.emit(loan); | |||
| this.onChange(loan); | |||
| this.Loan = loan; | |||
| } | |||
| // update changes | |||
| if (loan === undefined || loan == null ){ | |||
| this.Loan = new LoanModel({}); | |||
| }else{ | |||
| @@ -6,7 +6,7 @@ export class PayInAAARowModel { | |||
| public Period: Date; | |||
| public LoanNumber: string; | |||
| public Settlement: Date; | |||
| public LoanAmount: number; | |||
| public LoanFacility: number; | |||
| public Balance: number; | |||
| public InTrail: number; | |||
| @@ -16,8 +16,8 @@ export class PayInAAARowModel { | |||
| this.Period = new Date(payload.Period); | |||
| this.LoanNumber = payload.LoanNumber || ''; | |||
| this.Settlement = new Date(payload.Settlement); | |||
| this.LoanAmount = payload.LoanAmount || 0; | |||
| this.Balance = payload.Balance || 0; | |||
| this.LoanFacility = payload.LoanFacility || 0; | |||
| this.Balance = payload.Balance || -1; | |||
| this.InTrail = payload.InTrail || 0; | |||
| this.matchedPayIn = payload.matchedPayIn || -1; | |||
| } | |||
| @@ -29,12 +29,22 @@ export class PayInAAARowModel { | |||
| const pi = new PayInModel({}); | |||
| pi.Lender = this.Lender; | |||
| pi.LoanNumber = this.LoanNumber; | |||
| pi.Amount = this.LoanAmount; | |||
| pi.Amount = this.LoanFacility; | |||
| pi.Settlement = this.Settlement; | |||
| pi.IncomeAmount = this.InTrail; | |||
| pi.IncomeType = 'Trail'; | |||
| pi.Balance = this.Balance; | |||
| pi.OffsetBalance = -1; | |||
| pi.Ts = this.Period; | |||
| return pi; | |||
| } | |||
| public isMatch(pi: PayInModel): boolean { | |||
| return this.LoanNumber === pi.LoanNumber && this.LoanNumber !== '' && | |||
| this.Lender === pi.Lender && this.Lender !== '' && | |||
| this.InTrail === pi.IncomeAmount && | |||
| this.Balance === pi.Balance && | |||
| 'Trail' === pi.IncomeType; | |||
| } | |||
| } | |||
| @@ -28,7 +28,7 @@ export class PayInListFilterModel { | |||
| } | |||
| const bigNumber = 9999999999; | |||
| this.Take = payload.Take || 10; | |||
| this.Take = payload.Take || 0; | |||
| this.Skip = payload.Skip || 0; | |||
| this.TsFrom = payload.TsFrom? new Date(payload.TsFrom) : new Date('1900-01-01'); | |||
| this.TsTo = payload.TsTo? new Date(payload.TsTo) : new Date('2038-01-01'); | |||
| @@ -41,6 +41,20 @@ export class PayInModel { | |||
| this.UploadId = payload.UploadId; | |||
| } | |||
| public isEqual(pi: PayInModel): boolean { | |||
| return pi !== undefined && pi !== null && | |||
| this.Amount === pi.Amount && | |||
| this.Balance === pi.Balance && | |||
| this.Lender === pi.Lender && | |||
| this.LoanId === pi.LoanId && | |||
| this.LoanNumber === pi.LoanNumber && | |||
| this.OffsetBalance === pi.OffsetBalance && | |||
| this.Settlement.getDate() === pi.Settlement.getDate() && | |||
| this.IncomeAmount === pi.IncomeAmount && | |||
| this.IncomeType === pi.IncomeType && | |||
| this.Ts.getDate() === pi.Ts.getDate() && | |||
| this.UploadId === pi.UploadId; | |||
| } | |||
| } | |||
| @@ -1,13 +0,0 @@ | |||
| import {BehaviorSubject} from 'rxjs'; | |||
| import {PayInModel} from '../models/pay-in.model'; | |||
| export class PayInObservable extends BehaviorSubject<PayInModel[]> { | |||
| constructor() { | |||
| super(null); | |||
| } | |||
| Next(pis: PayInModel[]): void { | |||
| super.next(pis); | |||
| } | |||
| } | |||
| @@ -6,12 +6,14 @@ | |||
| <kendo-grid-column field="Period" format='{0:yyyy MMMM}'> </kendo-grid-column> | |||
| <kendo-grid-column field="LoanNumber"> </kendo-grid-column> | |||
| <kendo-grid-column field="Settlement" format='{0:MM/dd/yyyy h:mm a}'> </kendo-grid-column> | |||
| <kendo-grid-column field="LoanAmount" format='{0:c}' > </kendo-grid-column> | |||
| <kendo-grid-column field="LoanFacility" format='{0:c}' > </kendo-grid-column> | |||
| <kendo-grid-column field="Balance" format='{0:c}' > </kendo-grid-column> | |||
| <kendo-grid-column field="InTrail" format='{0:c}'> </kendo-grid-column> | |||
| <kendo-grid-column field="matchedPayIn" title="Matched" format='{0:c}'> | |||
| <ng-template kendoGridCellTemplate let-dataItem> | |||
| <button kendoButton iconClass="fa fa-calendar fa-fw"></button> | |||
| <kendo-icon *ngIf="dataItem.matchedPayIn === -1" [name]="'close-circle'" [size]="'small'" [themeColor]="'warning'"></kendo-icon> | |||
| <kendo-icon *ngIf="dataItem.matchedPayIn !== -1" [name]="'tick'" [size]="'small'" [themeColor]="'success'"></kendo-icon> | |||
| {{ dataItem.matchedPayIn === -1? 'Not matched': dataItem.matchedPayIn.Id}} | |||
| </ng-template> | |||
| </kendo-grid-column> | |||
| @@ -24,15 +24,19 @@ export class LenderAaaIncomeComponent implements OnInit, OnChanges { | |||
| this.analysis.AAA.forEach(v => { | |||
| v.matchedPayIn = this.matchPayIn(v); | |||
| this.data.push(new PayInAAARowModel(v)); | |||
| v.InTrail = 20; | |||
| this.data.push(new PayInAAARowModel(v)); | |||
| }); | |||
| console.log(this); | |||
| } | |||
| ngOnChanges(changes): void { | |||
| if (changes.newPayInUpdate){ | |||
| const piNew = changes.newPayInUpdate.currentValue as PayInModel; | |||
| this.data.forEach(v => { | |||
| // remove old matches | |||
| if (v.matchedPayIn !== -1 && v.matchedPayIn.Id === piNew.Id){ | |||
| v.matchedPayIn = -1; | |||
| } | |||
| // add new matches | |||
| if ( this.isMatch(v, changes.newPayInUpdate.currentValue) ){ | |||
| v.matchedPayIn = changes.newPayInUpdate.currentValue; | |||
| } | |||
| @@ -54,12 +58,10 @@ export class LenderAaaIncomeComponent implements OnInit, OnChanges { | |||
| if ( v === null || pi === null ){ | |||
| return false; | |||
| } | |||
| return v.LoanNumber === pi.LoanNumber && | |||
| this.Lender === pi.Lender && | |||
| v.InTrail === pi.IncomeAmount && | |||
| 'Trail' === pi.IncomeType; | |||
| return v.isMatch(pi); | |||
| } | |||
| @Input() set payIn(pis: PayInModel[]) { | |||
| this.payInCandidates = []; | |||
| pis.forEach( v => this.payInCandidates.push(new PayInModel(v))); | |||
| @@ -68,7 +70,6 @@ export class LenderAaaIncomeComponent implements OnInit, OnChanges { | |||
| v.matchedPayIn = this.matchPayIn(v); | |||
| }); | |||
| console.log ('new payin comes in'); | |||
| } | |||
| get PayIn(): PayInModel[] { | |||
| @@ -13,6 +13,7 @@ | |||
| kendoGridSelectBy | |||
| [selectedKeys]="gridSelection" | |||
| [rowClass]="rowClassCallbackBind" | |||
| (add)="addHandler($event)" | |||
| (cancel)="cancelHandler($event)" | |||
| @@ -22,6 +23,7 @@ | |||
| (pageChange)="pageChange($event)" | |||
| (sortChange)="sortChange($event)" | |||
| [ngClass]="{ 'filterByUploadMeta': uploadMeta.Id > 0 }" | |||
| > | |||
| <ng-template kendoGridToolbarTemplate> | |||
| <div style="width:100%; margin:0px; display:block"> | |||
| @@ -39,6 +41,7 @@ | |||
| <kendo-grid-command-column *ngIf="allowEdit" title="command" width="100"> | |||
| <ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem> | |||
| <div class="duplicate">dup</div> | |||
| <button kendoGridEditCommand *ngIf="!dataItem.Uploads !== 0" icon="edit"></button> | |||
| <button kendoGridRemoveCommand *ngIf="!dataItem.Uploads !== 0" icon="delete"></button> | |||
| <button kendoGridSaveCommand [disabled]="incomeFormGroup?.invalid" icon="save"></button> | |||
| @@ -55,9 +58,9 @@ | |||
| </kendo-grid-column> | |||
| <kendo-grid-column field="Lender" title="Lender" width="150" > | |||
| <ng-template kendoGridEditTemplate let-dataItem> | |||
| <ng-template kendoGridEditTemplate let-dataItem let-formGroup> | |||
| <kendo-combobox | |||
| [formControl]="incomeFormGroup.get('Lender')" | |||
| [formControl]="formGroup.get('Lender')" | |||
| [data]="lenderListView | async" | |||
| [loading]="lenderNameService.loading"> | |||
| </kendo-combobox> | |||
| @@ -14,6 +14,20 @@ kendo-grid { | |||
| margin-right: 10px; | |||
| } | |||
| } | |||
| div.duplicate { | |||
| background: darkgrey; | |||
| position: absolute; | |||
| top: 0px; | |||
| transform: rotate( | |||
| -45deg | |||
| ); | |||
| display: none; | |||
| width: 50px; | |||
| left: -15px; | |||
| text-align: center; | |||
| color: white; | |||
| } | |||
| } | |||
| .balance { | |||
| @@ -6,7 +6,7 @@ import {PayInService} from '../service/pay-in.service'; | |||
| import {PayInListResult} from '../models/pay-in-list-result.model'; | |||
| import {Router} from '@angular/router'; | |||
| import {PopupIncomeFilterComponent} from '../popup-income-filter/popup-income-filter.component'; | |||
| import {GridComponent, PageChangeEvent, SortSettings} from '@progress/kendo-angular-grid'; | |||
| import {GridComponent, PageChangeEvent, RowClassArgs, SortSettings} from '@progress/kendo-angular-grid'; | |||
| import {SortDescriptor} from '@progress/kendo-data-query'; | |||
| import {UploadMetaModel} from '../models/uploadMetaModel'; | |||
| import {debounce} from 'ts-debounce'; | |||
| @@ -47,6 +47,7 @@ export class PayInComponent implements OnInit { | |||
| public showOffsetBalance = true; | |||
| @Input() public pageable = true; | |||
| @Input() public pageSize = 15; | |||
| public gridSelection: number[] = []; | |||
| public sortable: SortSettings = { | |||
| @@ -55,15 +56,21 @@ export class PayInComponent implements OnInit { | |||
| public loading = false; | |||
| public lenderListView: Observable<string[]>; | |||
| public rowClassCallbackBind: any; | |||
| private debouncedLoadFilteredData = () => {}; | |||
| constructor(private pis: PayInService, | |||
| private lss: LoanSingleService, | |||
| private router: Router, | |||
| private lenderNameService: LenderNameService) { | |||
| this.lenderListView = this.lenderNameService; | |||
| this.lenderNameService.query(); | |||
| if ( this.pageable ) { | |||
| this.filter.Take = this.pageSize; | |||
| } | |||
| this.rowClassCallbackBind = this.rowClassCallback.bind(this); | |||
| } | |||
| ngOnInit(): void { | |||
| @@ -100,7 +107,7 @@ export class PayInComponent implements OnInit { | |||
| this.filter.UploadIds = [ value.Id.toString() ]; | |||
| } | |||
| this.debouncedLoadFilteredData(); | |||
| //console.log('upload filter changed', value, this.filter); | |||
| // console.log('upload filter changed', value, this.filter); | |||
| } | |||
| get uploadMeta(): UploadMetaModel { | |||
| @@ -143,10 +150,13 @@ export class PayInComponent implements OnInit { | |||
| this.showOffsetBalance = offsetBalance >= 0 ; | |||
| this.incomeFormGroup = this.createFormGroup(new PayInModelEx({})); | |||
| // console.log(this.incomeFormGroup); | |||
| sender.addRow(this.incomeFormGroup); | |||
| } | |||
| private createFormGroup(dataItem: PayInModelEx): FormGroup { | |||
| const Loan = this.filterLoan.Id !== '' ? this.filterLoan : dataItem.Loan; | |||
| const UploadMeta = this.filterUploadMeta.Id !== 0 ? this.filterUploadMeta : dataItem.UploadMeta ; | |||
| @@ -178,7 +188,6 @@ export class PayInComponent implements OnInit { | |||
| } | |||
| public cancelHandler({ sender, rowIndex }): void { | |||
| // console.log(sender); | |||
| this.closeEditor(sender, rowIndex); | |||
| } | |||
| @@ -195,6 +204,7 @@ export class PayInComponent implements OnInit { | |||
| this.Loan.cuPayIn( model); | |||
| this.updatePiInGrid(model); | |||
| this.Updated.emit(model); | |||
| console.log(this); | |||
| }, | |||
| err => { | |||
| // TODO: this.errorOccurred.emit('Error saving Income'); | |||
| @@ -253,7 +263,9 @@ export class PayInComponent implements OnInit { | |||
| } | |||
| private closeEditor(grid, rowIndex = this.editedRowIndex): void{ | |||
| grid.closeRow(rowIndex); | |||
| if ( rowIndex !== undefined ){ | |||
| grid.closeRow(rowIndex); | |||
| } | |||
| this.editedRowIndex = undefined; | |||
| this.incomeFormGroup = undefined; | |||
| } | |||
| @@ -310,12 +322,15 @@ export class PayInComponent implements OnInit { | |||
| } | |||
| public onLoanChange(loan: LoanModel): void { | |||
| if ( loan.Id !== '' ) { | |||
| if ( loan !== undefined && loan.Id !== '' ) { | |||
| this.incomeFormGroup.get('Lender').setValue(loan.Lender); | |||
| this.incomeFormGroup.get('Lender').disable({onlySelf: true, emitEvent: false}); | |||
| this.incomeFormGroup.get('LoanNumber').setValue(loan.LenderLoanNumber); | |||
| this.incomeFormGroup.get('LoanNumber').disable({onlySelf: true, emitEvent: false}); | |||
| }else{ | |||
| this.incomeFormGroup.get('Lender').enable({onlySelf: true, emitEvent: false}); | |||
| this.incomeFormGroup.get('LoanNumber').enable({onlySelf: true, emitEvent: false}); | |||
| } | |||
| } | |||
| @@ -326,6 +341,7 @@ export class PayInComponent implements OnInit { | |||
| } | |||
| public selectOrAddPayIn(pi: PayInModel): void{ | |||
| const row = this.gridData.data.findIndex( v => { | |||
| return v.LoanNumber === pi.LoanNumber && | |||
| v.Lender === pi.Lender && | |||
| @@ -336,9 +352,33 @@ export class PayInComponent implements OnInit { | |||
| if ( row >= 0 ) { // we found it | |||
| this.ScrollTo(row); | |||
| } else{ | |||
| this.gridSelection = []; // select none | |||
| this.incomeFormGroup = this.createFormGroup(new PayInModelEx(pi)); | |||
| // this.closeEditor(this.grid); | |||
| this.grid.addRow(this.incomeFormGroup); | |||
| } | |||
| } | |||
| public rowClassCallback( context: RowClassArgs): any { | |||
| const pi = context.dataItem as PayInModel; | |||
| const rowIndex = context.index; | |||
| const duplicate = this.checkDup(pi, rowIndex); | |||
| return { | |||
| dup: duplicate, | |||
| missingLoan: pi.LoanId === '' | |||
| }; | |||
| } | |||
| private checkDup(pi: PayInModel, rowIndex: number): boolean { | |||
| let found = false; | |||
| if (this.uploadMeta.Id <= 0 ) { | |||
| return false; // dup check is only available for upload filtered situation. | |||
| } | |||
| this.gridData.data.forEach( (v, index) => { | |||
| if ( v.isEqual(pi) && index !== rowIndex ){ | |||
| found = true; | |||
| } | |||
| }); | |||
| return found; | |||
| } | |||
| } | |||