| .upload-details div[role="tabpanel"]{ | .upload-details div[role="tabpanel"]{ | ||||
| padding:0px; | 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; | |||||
| } | |||||
| }; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| onLoanSelected(loan: LoanModel): void { | 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.valueChange.emit(loan); | ||||
| this.onChange(loan); | this.onChange(loan); | ||||
| this.Loan = loan; | |||||
| } | } | ||||
| // update changes | |||||
| if (loan === undefined || loan == null ){ | if (loan === undefined || loan == null ){ | ||||
| this.Loan = new LoanModel({}); | this.Loan = new LoanModel({}); | ||||
| }else{ | }else{ |
| public Period: Date; | public Period: Date; | ||||
| public LoanNumber: string; | public LoanNumber: string; | ||||
| public Settlement: Date; | public Settlement: Date; | ||||
| public LoanAmount: number; | |||||
| public LoanFacility: number; | |||||
| public Balance: number; | public Balance: number; | ||||
| public InTrail: number; | public InTrail: number; | ||||
| this.Period = new Date(payload.Period); | this.Period = new Date(payload.Period); | ||||
| this.LoanNumber = payload.LoanNumber || ''; | this.LoanNumber = payload.LoanNumber || ''; | ||||
| this.Settlement = new Date(payload.Settlement); | 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.InTrail = payload.InTrail || 0; | ||||
| this.matchedPayIn = payload.matchedPayIn || -1; | this.matchedPayIn = payload.matchedPayIn || -1; | ||||
| } | } | ||||
| const pi = new PayInModel({}); | const pi = new PayInModel({}); | ||||
| pi.Lender = this.Lender; | pi.Lender = this.Lender; | ||||
| pi.LoanNumber = this.LoanNumber; | pi.LoanNumber = this.LoanNumber; | ||||
| pi.Amount = this.LoanAmount; | |||||
| pi.Amount = this.LoanFacility; | |||||
| pi.Settlement = this.Settlement; | pi.Settlement = this.Settlement; | ||||
| pi.IncomeAmount = this.InTrail; | pi.IncomeAmount = this.InTrail; | ||||
| pi.IncomeType = 'Trail'; | pi.IncomeType = 'Trail'; | ||||
| pi.Balance = this.Balance; | |||||
| pi.OffsetBalance = -1; | |||||
| pi.Ts = this.Period; | pi.Ts = this.Period; | ||||
| return pi; | 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; | |||||
| } | |||||
| } | } |
| } | } | ||||
| const bigNumber = 9999999999; | const bigNumber = 9999999999; | ||||
| this.Take = payload.Take || 10; | |||||
| this.Take = payload.Take || 0; | |||||
| this.Skip = payload.Skip || 0; | this.Skip = payload.Skip || 0; | ||||
| this.TsFrom = payload.TsFrom? new Date(payload.TsFrom) : new Date('1900-01-01'); | 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'); | this.TsTo = payload.TsTo? new Date(payload.TsTo) : new Date('2038-01-01'); |
| this.UploadId = payload.UploadId; | 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; | |||||
| } | |||||
| } | } | ||||
| 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); | |||||
| } | |||||
| } |
| <kendo-grid-column field="Period" format='{0:yyyy MMMM}'> </kendo-grid-column> | <kendo-grid-column field="Period" format='{0:yyyy MMMM}'> </kendo-grid-column> | ||||
| <kendo-grid-column field="LoanNumber"> </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="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="Balance" format='{0:c}' > </kendo-grid-column> | ||||
| <kendo-grid-column field="InTrail" 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}'> | <kendo-grid-column field="matchedPayIn" title="Matched" format='{0:c}'> | ||||
| <ng-template kendoGridCellTemplate let-dataItem> | <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}} | {{ dataItem.matchedPayIn === -1? 'Not matched': dataItem.matchedPayIn.Id}} | ||||
| </ng-template> | </ng-template> | ||||
| </kendo-grid-column> | </kendo-grid-column> |
| this.analysis.AAA.forEach(v => { | this.analysis.AAA.forEach(v => { | ||||
| v.matchedPayIn = this.matchPayIn(v); | v.matchedPayIn = this.matchPayIn(v); | ||||
| this.data.push(new PayInAAARowModel(v)); | this.data.push(new PayInAAARowModel(v)); | ||||
| v.InTrail = 20; | |||||
| this.data.push(new PayInAAARowModel(v)); | |||||
| }); | }); | ||||
| console.log(this); | |||||
| } | } | ||||
| ngOnChanges(changes): void { | ngOnChanges(changes): void { | ||||
| if (changes.newPayInUpdate){ | if (changes.newPayInUpdate){ | ||||
| const piNew = changes.newPayInUpdate.currentValue as PayInModel; | |||||
| this.data.forEach(v => { | 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) ){ | if ( this.isMatch(v, changes.newPayInUpdate.currentValue) ){ | ||||
| v.matchedPayIn = changes.newPayInUpdate.currentValue; | v.matchedPayIn = changes.newPayInUpdate.currentValue; | ||||
| } | } | ||||
| if ( v === null || pi === null ){ | if ( v === null || pi === null ){ | ||||
| return false; | 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[]) { | @Input() set payIn(pis: PayInModel[]) { | ||||
| this.payInCandidates = []; | this.payInCandidates = []; | ||||
| pis.forEach( v => this.payInCandidates.push(new PayInModel(v))); | pis.forEach( v => this.payInCandidates.push(new PayInModel(v))); | ||||
| v.matchedPayIn = this.matchPayIn(v); | v.matchedPayIn = this.matchPayIn(v); | ||||
| }); | }); | ||||
| console.log ('new payin comes in'); | |||||
| } | } | ||||
| get PayIn(): PayInModel[] { | get PayIn(): PayInModel[] { |
| kendoGridSelectBy | kendoGridSelectBy | ||||
| [selectedKeys]="gridSelection" | [selectedKeys]="gridSelection" | ||||
| [rowClass]="rowClassCallbackBind" | |||||
| (add)="addHandler($event)" | (add)="addHandler($event)" | ||||
| (cancel)="cancelHandler($event)" | (cancel)="cancelHandler($event)" | ||||
| (pageChange)="pageChange($event)" | (pageChange)="pageChange($event)" | ||||
| (sortChange)="sortChange($event)" | (sortChange)="sortChange($event)" | ||||
| [ngClass]="{ 'filterByUploadMeta': uploadMeta.Id > 0 }" | |||||
| > | > | ||||
| <ng-template kendoGridToolbarTemplate> | <ng-template kendoGridToolbarTemplate> | ||||
| <div style="width:100%; margin:0px; display:block"> | <div style="width:100%; margin:0px; display:block"> | ||||
| <kendo-grid-command-column *ngIf="allowEdit" title="command" width="100"> | <kendo-grid-command-column *ngIf="allowEdit" title="command" width="100"> | ||||
| <ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem> | <ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem> | ||||
| <div class="duplicate">dup</div> | |||||
| <button kendoGridEditCommand *ngIf="!dataItem.Uploads !== 0" icon="edit"></button> | <button kendoGridEditCommand *ngIf="!dataItem.Uploads !== 0" icon="edit"></button> | ||||
| <button kendoGridRemoveCommand *ngIf="!dataItem.Uploads !== 0" icon="delete"></button> | <button kendoGridRemoveCommand *ngIf="!dataItem.Uploads !== 0" icon="delete"></button> | ||||
| <button kendoGridSaveCommand [disabled]="incomeFormGroup?.invalid" icon="save"></button> | <button kendoGridSaveCommand [disabled]="incomeFormGroup?.invalid" icon="save"></button> | ||||
| </kendo-grid-column> | </kendo-grid-column> | ||||
| <kendo-grid-column field="Lender" title="Lender" width="150" > | <kendo-grid-column field="Lender" title="Lender" width="150" > | ||||
| <ng-template kendoGridEditTemplate let-dataItem> | |||||
| <ng-template kendoGridEditTemplate let-dataItem let-formGroup> | |||||
| <kendo-combobox | <kendo-combobox | ||||
| [formControl]="incomeFormGroup.get('Lender')" | |||||
| [formControl]="formGroup.get('Lender')" | |||||
| [data]="lenderListView | async" | [data]="lenderListView | async" | ||||
| [loading]="lenderNameService.loading"> | [loading]="lenderNameService.loading"> | ||||
| </kendo-combobox> | </kendo-combobox> |
| margin-right: 10px; | 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 { | .balance { |
| import {PayInListResult} from '../models/pay-in-list-result.model'; | import {PayInListResult} from '../models/pay-in-list-result.model'; | ||||
| import {Router} from '@angular/router'; | import {Router} from '@angular/router'; | ||||
| import {PopupIncomeFilterComponent} from '../popup-income-filter/popup-income-filter.component'; | 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 {SortDescriptor} from '@progress/kendo-data-query'; | ||||
| import {UploadMetaModel} from '../models/uploadMetaModel'; | import {UploadMetaModel} from '../models/uploadMetaModel'; | ||||
| import {debounce} from 'ts-debounce'; | import {debounce} from 'ts-debounce'; | ||||
| public showOffsetBalance = true; | public showOffsetBalance = true; | ||||
| @Input() public pageable = true; | @Input() public pageable = true; | ||||
| @Input() public pageSize = 15; | |||||
| public gridSelection: number[] = []; | public gridSelection: number[] = []; | ||||
| public sortable: SortSettings = { | public sortable: SortSettings = { | ||||
| public loading = false; | public loading = false; | ||||
| public lenderListView: Observable<string[]>; | public lenderListView: Observable<string[]>; | ||||
| public rowClassCallbackBind: any; | |||||
| private debouncedLoadFilteredData = () => {}; | private debouncedLoadFilteredData = () => {}; | ||||
| constructor(private pis: PayInService, | constructor(private pis: PayInService, | ||||
| private lss: LoanSingleService, | private lss: LoanSingleService, | ||||
| private router: Router, | private router: Router, | ||||
| private lenderNameService: LenderNameService) { | private lenderNameService: LenderNameService) { | ||||
| this.lenderListView = this.lenderNameService; | this.lenderListView = this.lenderNameService; | ||||
| this.lenderNameService.query(); | this.lenderNameService.query(); | ||||
| if ( this.pageable ) { | |||||
| this.filter.Take = this.pageSize; | |||||
| } | |||||
| this.rowClassCallbackBind = this.rowClassCallback.bind(this); | |||||
| } | } | ||||
| ngOnInit(): void { | ngOnInit(): void { | ||||
| this.filter.UploadIds = [ value.Id.toString() ]; | this.filter.UploadIds = [ value.Id.toString() ]; | ||||
| } | } | ||||
| this.debouncedLoadFilteredData(); | this.debouncedLoadFilteredData(); | ||||
| //console.log('upload filter changed', value, this.filter); | |||||
| // console.log('upload filter changed', value, this.filter); | |||||
| } | } | ||||
| get uploadMeta(): UploadMetaModel { | get uploadMeta(): UploadMetaModel { | ||||
| this.showOffsetBalance = offsetBalance >= 0 ; | this.showOffsetBalance = offsetBalance >= 0 ; | ||||
| this.incomeFormGroup = this.createFormGroup(new PayInModelEx({})); | this.incomeFormGroup = this.createFormGroup(new PayInModelEx({})); | ||||
| // console.log(this.incomeFormGroup); | // console.log(this.incomeFormGroup); | ||||
| sender.addRow(this.incomeFormGroup); | sender.addRow(this.incomeFormGroup); | ||||
| } | } | ||||
| private createFormGroup(dataItem: PayInModelEx): FormGroup { | private createFormGroup(dataItem: PayInModelEx): FormGroup { | ||||
| const Loan = this.filterLoan.Id !== '' ? this.filterLoan : dataItem.Loan; | const Loan = this.filterLoan.Id !== '' ? this.filterLoan : dataItem.Loan; | ||||
| const UploadMeta = this.filterUploadMeta.Id !== 0 ? this.filterUploadMeta : dataItem.UploadMeta ; | const UploadMeta = this.filterUploadMeta.Id !== 0 ? this.filterUploadMeta : dataItem.UploadMeta ; | ||||
| } | } | ||||
| public cancelHandler({ sender, rowIndex }): void { | public cancelHandler({ sender, rowIndex }): void { | ||||
| // console.log(sender); | |||||
| this.closeEditor(sender, rowIndex); | this.closeEditor(sender, rowIndex); | ||||
| } | } | ||||
| this.Loan.cuPayIn( model); | this.Loan.cuPayIn( model); | ||||
| this.updatePiInGrid(model); | this.updatePiInGrid(model); | ||||
| this.Updated.emit(model); | this.Updated.emit(model); | ||||
| console.log(this); | |||||
| }, | }, | ||||
| err => { | err => { | ||||
| // TODO: this.errorOccurred.emit('Error saving Income'); | // TODO: this.errorOccurred.emit('Error saving Income'); | ||||
| } | } | ||||
| private closeEditor(grid, rowIndex = this.editedRowIndex): void{ | private closeEditor(grid, rowIndex = this.editedRowIndex): void{ | ||||
| grid.closeRow(rowIndex); | |||||
| if ( rowIndex !== undefined ){ | |||||
| grid.closeRow(rowIndex); | |||||
| } | |||||
| this.editedRowIndex = undefined; | this.editedRowIndex = undefined; | ||||
| this.incomeFormGroup = undefined; | this.incomeFormGroup = undefined; | ||||
| } | } | ||||
| } | } | ||||
| public onLoanChange(loan: LoanModel): void { | public onLoanChange(loan: LoanModel): void { | ||||
| if ( loan.Id !== '' ) { | |||||
| if ( loan !== undefined && loan.Id !== '' ) { | |||||
| this.incomeFormGroup.get('Lender').setValue(loan.Lender); | this.incomeFormGroup.get('Lender').setValue(loan.Lender); | ||||
| this.incomeFormGroup.get('Lender').disable({onlySelf: true, emitEvent: false}); | this.incomeFormGroup.get('Lender').disable({onlySelf: true, emitEvent: false}); | ||||
| this.incomeFormGroup.get('LoanNumber').setValue(loan.LenderLoanNumber); | this.incomeFormGroup.get('LoanNumber').setValue(loan.LenderLoanNumber); | ||||
| this.incomeFormGroup.get('LoanNumber').disable({onlySelf: true, emitEvent: false}); | 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}); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| public selectOrAddPayIn(pi: PayInModel): void{ | public selectOrAddPayIn(pi: PayInModel): void{ | ||||
| const row = this.gridData.data.findIndex( v => { | const row = this.gridData.data.findIndex( v => { | ||||
| return v.LoanNumber === pi.LoanNumber && | return v.LoanNumber === pi.LoanNumber && | ||||
| v.Lender === pi.Lender && | v.Lender === pi.Lender && | ||||
| if ( row >= 0 ) { // we found it | if ( row >= 0 ) { // we found it | ||||
| this.ScrollTo(row); | this.ScrollTo(row); | ||||
| } else{ | } else{ | ||||
| this.gridSelection = []; // select none | |||||
| this.incomeFormGroup = this.createFormGroup(new PayInModelEx(pi)); | this.incomeFormGroup = this.createFormGroup(new PayInModelEx(pi)); | ||||
| // this.closeEditor(this.grid); | |||||
| this.grid.addRow(this.incomeFormGroup); | 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; | |||||
| } | |||||
| } | } |