Browse Source

2.04 wrap up

tags/2.037
Patrick Sun 4 years ago
parent
commit
d61f740325
37 changed files with 797 additions and 339 deletions
  1. +1
    -1
      package-lock.json
  2. +2
    -2
      package.json
  3. +3
    -0
      src/app/app-routing.module.ts
  4. +3
    -3
      src/app/app.module.ts
  5. +17
    -4
      src/app/list-all-rewards/list-all-rewards.component.html
  6. +7
    -0
      src/app/list-all-rewards/list-all-rewards.component.scss
  7. +60
    -25
      src/app/list-all-rewards/list-all-rewards.component.ts
  8. +1
    -1
      src/app/loan-edit/loan-edit.component.ts
  9. +3
    -6
      src/app/loan-edit/people-reward/people-reward.component.html
  10. +11
    -9
      src/app/loan-edit/people-reward/people-reward.component.ts
  11. +1
    -2
      src/app/main-menu-items.ts
  12. +25
    -15
      src/app/models/loan.model.ts
  13. +0
    -2
      src/app/models/pay-in.model.ts
  14. +0
    -18
      src/app/models/payout-audit.model.ts
  15. +26
    -2
      src/app/models/payout.ex.model.ts
  16. +38
    -0
      src/app/models/payout.model.ts
  17. +2
    -1
      src/app/models/people.model.ts
  18. +7
    -0
      src/app/models/reward-ex-list-result.model.ts
  19. +72
    -10
      src/app/models/reward-ex.model.ts
  20. +13
    -13
      src/app/models/reward.model.ts
  21. +2
    -1
      src/app/models/userExModel.ts
  22. +12
    -7
      src/app/pay-in/pay-in.component.ts
  23. +92
    -28
      src/app/pay-out-details/pay-out-details.component.html
  24. +41
    -2
      src/app/pay-out-details/pay-out-details.component.scss
  25. +149
    -14
      src/app/pay-out-details/pay-out-details.component.ts
  26. +4
    -4
      src/app/people-select/people-select.component.html
  27. +43
    -158
      src/app/people-select/people-select.component.ts
  28. +1
    -1
      src/app/reward-paid/reward-paid.component.html
  29. +4
    -3
      src/app/reward-paid/reward-paid.component.ts
  30. +5
    -1
      src/app/reward-select/reward-select.component.html
  31. +25
    -2
      src/app/reward-select/reward-select.component.ts
  32. +30
    -1
      src/app/service/payout.service.ts
  33. +6
    -0
      src/app/service/people.service.ts
  34. +35
    -0
      src/app/service/reward.service.ts
  35. +27
    -1
      src/app/single-payout-rewards-list/single-payout-rewards-list.component.html
  36. +24
    -0
      src/app/single-payout-rewards-list/single-payout-rewards-list.component.scss
  37. +5
    -2
      src/app/single-payout-rewards-list/single-payout-rewards-list.component.ts

+ 1
- 1
package-lock.json View File

@@ -1,6 +1,6 @@
{
"name": "broker",
"version": "2.0.1",
"version": "2.0.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

+ 2
- 2
package.json View File

@@ -1,12 +1,12 @@
{
"name": "broker",
"version": "2.0.1",
"version": "2.0.4",
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json",
"prebuild": "npm --no-git-tag-version version patch",
"build": "ng build ",
"buildsfm": "ng build --prod --base-href=/broker/ --deploy-url=/broker/ ",
"buildsfm": "npm --no-git-tag-version version patch && ng build --prod --base-href=/broker/ --deploy-url=/broker/ ",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"

+ 3
- 0
src/app/app-routing.module.ts View File

@@ -28,6 +28,7 @@ import {ListIncomeComponent} from './list-income/list-income.component';
import {UploadDetailComponent} from './upload-detail/upload-detail.component';
import {LoansAllComponent} from './loans-all/loans-all.component';
import {RewardsAllComponent} from './rewards-all/rewards-all.component';
import {PayOutDetailsComponent} from './pay-out-details/pay-out-details.component';


const routes: Routes = [
@@ -60,6 +61,8 @@ const routes: Routes = [
{path : 'profile/:id', component: ProfileComponent, canActivate: [AuthGuard] },
{path : 'upload-details/:id', component: UploadDetailComponent, canActivate: [AuthGuard] },
{path : 'list-income', component: ListIncomeComponent, canActivate: [AuthGuard] },
{path : 'payout-details/:id', component: PayOutDetailsComponent, canActivate: [AuthGuard] },
{path : 'reward-paid/:id', component: RewardPaidComponent, canActivate: [AuthGuard] },
{path : 'e403', component: E403Component },
];


+ 3
- 3
src/app/app.module.ts View File

@@ -1,12 +1,12 @@

//Angular
// Angular
import {APP_INITIALIZER, NgModule} from '@angular/core';
import { CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
//Kendo
// Kendo
import { MenuModule, ContextMenuModule } from '@progress/kendo-angular-menu';
import { IconsModule } from '@progress/kendo-angular-icons';
import { DialogsModule } from '@progress/kendo-angular-dialog';
@@ -14,7 +14,7 @@ import {ButtonsModule, FloatingActionButtonModule} from '@progress/kendo-angular
import { GridModule, PDFModule, ExcelModule } from '@progress/kendo-angular-grid';
import { InputsModule } from '@progress/kendo-angular-inputs';

//App
// App
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { DashboardComponent } from './dashboard/dashboard.component';

+ 17
- 4
src/app/list-all-rewards/list-all-rewards.component.html View File

@@ -2,7 +2,7 @@
[data]="gridView"
[loading]="loading"
[sortable]="true"
[pageable]="true"
[pageable]="pageable"
[filterable]="true"
[resizable]="true"
[selectable]="selectTableSettings"
@@ -10,15 +10,20 @@
kendoGridSelectBy="Id"
[selectedKeys]="selected"

(selectionChange)="onSelectionChange($event)"
[pageSize]="state.take"
[skip]="state.skip"
[sort]="state.sort"
[filter]="state.filter"
(dataStateChange)="dataStateChange($event)"

class="fullheight_grid"
>
<ng-template kendoGridToolbarTemplate [position]="'top'">
<button (click)="onSelectionEmit()" class="k-button">Make Payment</button>
<div class="payment-toolbar">
<button *ngIf="showNonPaidOnly" (click)="onSelectionEmit()" class="k-button">Make Payment</button>
<span *ngIf="!showNonPaidOnly"> Today: {{ today | date }}</span>
</div>
</ng-template>

<kendo-grid-checkbox-column *ngIf="EnableSelectButton" width="50" ></kendo-grid-checkbox-column>
@@ -44,7 +49,7 @@
</kendo-grid-column>
<kendo-grid-column field="ToDisplay" title="Beneficiary" width="250">
<ng-template kendoGridCellTemplate let-dataItem>
<div class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.To)}"></div>
<div class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.ToId)}"></div>
<div class="customer-name"> {{ dataItem.ToDisplay }}</div>
</ng-template>
<ng-template kendoGridFilterCellTemplate let-filter let-column="column">
@@ -89,7 +94,15 @@
</kendo-grid-column>


<kendo-grid-column field="Paid" title="Paid">
<kendo-grid-column field="Paid" title="Invoice" [sortable]="false" [filterable]="false" width="100">
<ng-template kendoGridCellTemplate let-dataItem>
<button *ngIf="dataItem.Paid" kendoButton [icon]="'attachment'"
(click)="onGoDetail(dataItem)"> {{dataItem.PayOutId}} </button>

<kendo-chip *ngIf="! dataItem.Paid" [label]="'$'" (click)="onGoPayment(dataItem)" >
</kendo-chip>

</ng-template>
<ng-template kendoGridFilterCellTemplate let-filter let-column="column">
<kendo-grid-boolean-filter-cell
[column]="column"

+ 7
- 0
src/app/list-all-rewards/list-all-rewards.component.scss View File

@@ -1,7 +1,14 @@
kendo-grid.fullheight_grid{
height: 100% !important;
div.payment-toolbar {
width: 100%;
text-align: right;
display:block;
}
}



.customer-photo{
display: inline-block;
width: 32px;

+ 60
- 25
src/app/list-all-rewards/list-all-rewards.component.ts View File

@@ -1,11 +1,12 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {AuthService} from '../service/auth.service';
import {HttpClient} from '@angular/common/http';
import {RewardByUserModel} from '../models/reward-by-user.model';
import {DataStateChangeEvent, GridDataResult, SelectableSettings,} from '@progress/kendo-angular-grid';
import {DataStateChangeEvent, GridDataResult, PagerSettings, SelectableSettings,} from '@progress/kendo-angular-grid';
import {State, process} from '@progress/kendo-data-query';
import {PayInModel} from '../models/pay-in.model';
import {RewardModel} from '../models/reward.model';
import {RewardExModel} from '../models/reward-ex.model';
import {SelectionEvent} from '@progress/kendo-angular-grid/dist/es2015/selection/types';
import {RewardService} from '../service/reward.service';
import {Router} from '@angular/router';

@Component({
selector: 'app-list-all-rewards',
@@ -13,9 +14,15 @@ import {RewardModel} from '../models/reward.model';
styleUrls: ['./list-all-rewards.component.scss']
})
export class ListAllRewardsComponent implements OnInit {
@Output() confirmSelection = new EventEmitter<RewardExModel[]>();
@Input() selectedRewards: RewardExModel[] = [];
@Input() showNonPaidOnly: false;
@Input() filterByPayOutId = 0;
@Input() filterByUserId = '';
public today = new Date();
public selected: number[] = [];


public gridData: RewardByUserModel[] = [] ;
public gridData: RewardExModel[] = [] ;
public gridView: GridDataResult;
public state: State = {
skip: 0,
@@ -24,6 +31,11 @@ export class ListAllRewardsComponent implements OnInit {
};
public loading = true;

@Input() public pageable: PagerSettings = {
pageSizes: [2, 5, 10, 15, 20, 30],
previousNext: true
};

public selectTableSettings: SelectableSettings | boolean = true;
// private multipleSelection: SelectableSettings = {
// checkboxOnly: false,
@@ -32,40 +44,45 @@ export class ListAllRewardsComponent implements OnInit {
// };

public EnableSelectButton = true;
@Input() selectedRewards: RewardModel[] = [];
public selected: number[] = [];

@Output() RewardsSelected: EventEmitter<PayInModel[]> = new EventEmitter<PayInModel[]>();

constructor(private auth: AuthService, private http: HttpClient) { }
constructor(private auth: AuthService, private rs: RewardService, private router: Router) { }

ngOnInit(): void {
this.loadInitRewardGrid();
this.EnableSelectButton = this.showNonPaidOnly;
if ( this.showNonPaidOnly ){
this.state.filter.filters.push({
field: 'PayOutId',
operator: 'isnull',
value: ''
});
}
this.selectedRewards.forEach(v => this.selected.push(v.Id));
this.loadRewardGrid();
}


public loadInitRewardGrid(): void {
public loadRewardGrid(): void {
this.loading = true;
this.gridData = [];
this.http.get<RewardByUserModel[]>(this.auth.getUrl('user-reward/')).subscribe(
rsp => {
this.gridView = {total: 0, data: []};
this.rs.getRewardExList(this.state).subscribe(
resp => {
this.gridView.total = resp.total;
resp.data.forEach( v => this.gridView.data.push(new RewardExModel(v)) );
this.loading = false;
}, error => {
this.loading = false;
}, () => {
this.loading = false;
rsp.forEach(v => {
this.gridData.push(new RewardByUserModel(v));
});
this.loadRewards();
}
);
}

private loadRewards(): void {
this.gridView = process(this.gridData, this.state);
}


public dataStateChange(state: DataStateChangeEvent): void {
this.state = state;
console.log(state);
this.loadRewards();
this.loadRewardGrid();
}

private photoURL(peopleId: any): string {
@@ -74,6 +91,24 @@ export class ListAllRewardsComponent implements OnInit {
}

public onSelectionEmit(): void {
console.log(this.selected);
this.confirmSelection.emit(this.selectedRewards);
}

public onSelectionChange(e: SelectionEvent): void {
this.selectedRewards = this.selectedRewards.filter( v => ! e.deselectedRows.find( deSel => v.Id === deSel.dataItem.Id ) );
e.selectedRows.forEach( v => {
if (! this.selectedRewards.find(existing => existing.Id === v.dataItem.Id) ){
this.selectedRewards.push(v.dataItem);
}
});
return;
}

public onGoDetail(item: RewardExModel): void{
this.router.navigate(['reward-paid/', item.PayOutId]).then();
}

public onGoPayment( item: RewardExModel): void{
this.router.navigate(['reward-paid/']).then();
}
}

+ 1
- 1
src/app/loan-edit/loan-edit.component.ts View File

@@ -102,7 +102,7 @@ export class LoanEditComponent implements OnInit {
public action(status): void {
this.dialogOpened = false;
if ( this.navigateTo !== '' ) {
this.router.navigate([this.navigateTo]);
this.router.navigate([this.navigateTo]).then();
}
}


+ 3
- 6
src/app/loan-edit/people-reward/people-reward.component.html View File

@@ -28,15 +28,12 @@
</ng-template>
</kendo-grid-command-column>

<kendo-grid-column field="To" title="Name" width="200">
<kendo-grid-column field="ToId" title="Name" width="200">
<ng-template kendoGridCellTemplate let-dataItem>
<div class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.To) }"></div>
<div class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.ToId) }"></div>
<div class="customer-name"> {{ UserName(dataItem) }}</div>
</ng-template>
<ng-template
kendoGridEditTemplate let-fg="formGroup" let-column="column" let-dataItem="dataItem">


<ng-template kendoGridEditTemplate let-fg="formGroup" let-column="column" let-dataItem="dataItem">

<kendo-multicolumncombobox
#selectRevelantPeople

+ 11
- 9
src/app/loan-edit/people-reward/people-reward.component.ts View File

@@ -10,13 +10,14 @@ import {ClonerService} from '../../service/clone.service';
import {PeopleService} from '../../service/people.service';
import {Observable, of} from 'rxjs';
import {map} from 'rxjs/operators';
import {RewardService} from '../../service/reward.service';



const createFormGroup = dataItem => new FormGroup({
Id: new FormControl(dataItem.Id),
To: new FormControl(dataItem.To, [Validators.required, Validators.maxLength(128), Validators.minLength(1)]),
From: new FormControl(dataItem.From),
ToId: new FormControl(dataItem.ToId, [Validators.required, Validators.maxLength(128), Validators.minLength(1)]),
FromId: new FormControl(dataItem.FromId),
Role: new FormControl({value: dataItem.Role, disabled: true} , [Validators.required, Validators.minLength(2), Validators.maxLength(40)]),
Amount: new FormControl(dataItem.Amount, Validators.compose([Validators.required, Validators.min(0), Validators.max(1000000)])),
Description: new FormControl(dataItem.Description, [Validators.required, Validators.maxLength(128)]),
@@ -95,8 +96,8 @@ export class PeopleRewardComponent implements OnInit {
this.closeEditor(sender);
this.formGroup = createFormGroup({
Id: 0,
To: '',
From: '',
ToId: '',
FromId: '',
Role: 'Unknown',
Amount: 0,
Description: '',
@@ -123,8 +124,8 @@ export class PeopleRewardComponent implements OnInit {
}

public saveHandler({ sender, rowIndex, formGroup, isNew }): void {
const reward = formGroup.value;
reward.From = '0'; // Admin
const reward: RewardModel = formGroup.value;
reward.FromId = '0'; // Admin

// reward.Ts = new Date(); // Now
reward.LoanId = this.Loan.Id; // Enforce LoanId
@@ -132,17 +133,18 @@ export class PeopleRewardComponent implements OnInit {
this.ls.saveReward(reward, isNew).subscribe(
resp => {
if ( reward.Id === 0 ) {
const r = this.Loan.addReward(resp.Amount, resp.Description, resp.Id, resp.LoanId, resp.PayOutId, resp.To, resp.From, resp.Ts);
const r = this.Loan.addReward(
resp.Amount, resp.Description, resp.Id, resp.LoanId, resp.PayOutId, resp.ToId, resp.FromId, resp.Ts);
this.pendingReward.unshift(r);
}else{
const idx = this.pendingReward.findIndex( v => v.Id === reward.Id);
// update
this.pendingReward[idx].To = reward.To;
this.pendingReward[idx].ToId = reward.ToId;
this.pendingReward[idx].Amount = reward.Amount;
this.pendingReward[idx].Description = reward.Description;
this.pendingReward[idx].Ts = reward.Ts;
// update Loan
this.Loan.updateReward(resp.Amount, resp.Description, resp.Id, resp.LoanId, resp.PayOutId, resp.To, resp.From, resp.Ts);
this.Loan.updateReward(resp.Amount, resp.Description, resp.Id, resp.LoanId, resp.PayOutId, resp.ToId, resp.FromId, resp.Ts);
}
}


+ 1
- 2
src/app/main-menu-items.ts View File

@@ -34,8 +34,7 @@ export const mainMenuItems: any[] = [
{ text: '--', separator: 'true' },
{ text: 'By Broker', icon: 'table', url: './#list-reward-by-broker' },
{ text: '--', separator: 'true' },
{ text: 'Not Paid', icon: 'attachment' , url: './#reward-unpaid'},
{ text: 'Paid', icon: 'attachment' , url: './#reward-paid'},
{ text: 'Invoice', icon: 'attachment' , url: './#reward-paid'},
]
},
{

+ 25
- 15
src/app/models/loan.model.ts View File

@@ -146,8 +146,8 @@ export class LoanModel {
reward.Id,
reward.LoanId,
reward.PayOutId,
reward.To,
reward.From,
reward.ToId,
reward.FromId,
reward.Ts);
});
return;
@@ -206,15 +206,15 @@ export class LoanModel {
});
}
public addReward(Amount: number, Description: string, Id: number,
LoanId: string, PayOutId: number, To: string , From: string, Ts: Date): RewardModel {
LoanId: string, PayOutId: number, ToId: string , FromId: string, Ts: Date): RewardModel {
const r = new RewardModel(
Amount,
Description,
Id,
LoanId,
PayOutId,
To,
From,
ToId,
FromId,
new Date(Ts),
this.callBacks()
);
@@ -224,15 +224,15 @@ export class LoanModel {
}

public updateReward(Amount: number, Description: string, Id: number,
LoanId: string, PayOutId: number, To: string , From: string, Ts: Date): RewardModel {
LoanId: string, PayOutId: number, ToId: string , FromId: string, Ts: Date): RewardModel {
const r = new RewardModel(
Amount,
Description,
Id,
LoanId,
PayOutId,
To,
From,
ToId,
FromId,
new Date(Ts),
this.callBacks()
);
@@ -242,8 +242,8 @@ export class LoanModel {
this.Reward[idx].Description = Description;
this.Reward[idx].LoanId = r.LoanId; // Ensure Loan Id is correct
this.Reward[idx].PayOutId = PayOutId;
this.Reward[idx].To = To;
this.Reward[idx].From = From;
this.Reward[idx].ToId = ToId;
this.Reward[idx].FromId = FromId;
this.Reward[idx].Ts = new Date(Ts);

return r;
@@ -318,24 +318,34 @@ export class LoanModel {
const result: RelevantPeopleModel[] = [];

this.Client.forEach(( c => {
result.push({Id: c.Id, Display: c.Display, Role: 'Client'} ) ;
if ( !result.find( existing => existing.Id === c.Id ) ){
result.push({Id: c.Id, Display: c.Display, Role: 'Client'} ) ;
}

}));

this.Broker.forEach(( c => {
result.push({Id: c.Id, Display: c.Display, Role: 'Broker'});
if ( !result.find( existing => existing.Id === c.Id ) ) {
result.push({Id: c.Id, Display: c.Display, Role: 'Broker'});
}
}));

this.OtherRewarder.forEach(( c => {
result.push({Id: c.Id, Display: c.Display, Role: this.getRoleById(c.Id)});
if ( !result.find( existing => existing.Id === c.Id ) ) {
result.push({Id: c.Id, Display: c.Display, Role: this.getRoleById(c.Id)});
}
}));

this.RewardPeople.forEach( c => {
result.push({Id: c.Id, Display: c.Display, Role: 'none'});
if ( !result.find( existing => existing.Id === c.Id ) ) {
result.push({Id: c.Id, Display: c.Display, Role: 'none'});
}
});

return [...new Set(result)]; // remove duplicates, if any
return result ; // [...new Set(result)]; // remove duplicates, if any
}


public getRoleById(id: string): string {
let role = '';
this.PeopleMap.every(v => {

+ 0
- 2
src/app/models/pay-in.model.ts View File

@@ -1,5 +1,3 @@
import {LoanModel} from './loan.model';
import {UploadMetaModel} from './uploadMetaModel';

export class PayInModel {
public Id: number;

+ 0
- 18
src/app/models/payout-audit.model.ts View File

@@ -1,18 +0,0 @@
export class PayoutAudit{
id: string;

//parent
payOut: string;

//date
when : Date;

//by who, at least 2
who : string ;

//for what? edit? paid?
what: string;

//ip
ip: string;
}

+ 26
- 2
src/app/models/payout.ex.model.ts View File

@@ -1,4 +1,6 @@
import {UserExModel} from './userExModel';
import {PayoutModel} from './payout.model';
import {RewardExModel} from './reward-ex.model';

export class PayOutExModel{
Id: number;
@@ -10,12 +12,14 @@ export class PayOutExModel{
PayDate: Date;
PayAmount: number;
Status: string;
constructor( payload: Partial<PayOutExModel>) {
Rewards?: RewardExModel[];
constructor( payload?: Partial<PayOutExModel>) {
if (! payload ) { payload = {}; }
this.Id = payload.Id || 0;
if (payload.Ts){
this.Ts = new Date( payload.Ts);
}else{
this.Ts = new Date('1800-01-01');
this.Ts = new Date();
}

this.To = new UserExModel(payload.To);
@@ -30,5 +34,25 @@ export class PayOutExModel{
}
this.PayAmount = payload.PayAmount || 0;
this.Status = payload.Status || '';

this.Rewards = [];
if ( payload.Rewards && payload.Rewards.length){
payload.Rewards.forEach( v => this.Rewards.push( new RewardExModel(v) ) );
}
}

public toPayOutModel(): PayoutModel {
const ret = new PayoutModel();
ret.Id = this.Id;
ret.Ts = this.Ts;
ret.To = this.To.Id;
ret.Prepared = this.Prepared.Id;
ret.Approved = this.Approved.Id;
ret.Paid = this.Paid.Id;
ret.PayDate = this.PayDate;
ret.PayAmount = this.PayAmount;
ret.Status = this.Status;
ret.RewardIds = this.Rewards.map( v => v.Id);
return ret;
}
}

+ 38
- 0
src/app/models/payout.model.ts View File

@@ -0,0 +1,38 @@
import {UserExModel} from './userExModel';

export class PayoutModel {
Id: number;
Ts: Date;
To: string;
Prepared: string;
Approved: string;
Paid: string;
PayDate: Date;
PayAmount: number;
Status: string;
RewardIds?: number[];
constructor( payload?: Partial<PayoutModel>){
if (! payload ) { payload = {}; }
this.Id = payload.Id || 0;
if ( payload.Ts ){
this.Ts = new Date(payload.Ts);
}else{
this.Ts = new Date('1900-01-01');
}
this.To = payload.To || '';
this.Prepared = payload.Prepared || '';
this.Approved = payload.Approved || '';
this.Paid = payload.Paid || '';
if (payload.PayDate){
this.PayDate = new Date(payload.PayDate);
}else{
this.PayDate = new Date('1900-01-01');
}
this.PayAmount = payload.PayAmount || 0;
this.Status = payload.Status || '';
this.RewardIds = [];
if ( Array.isArray(payload) ) {
this.RewardIds = payload.RewardIds;
}
}
}

+ 2
- 1
src/app/models/people.model.ts View File

@@ -9,7 +9,8 @@ export class PeopleModel{
public Nick: string;
public Enabled: boolean;

constructor(payload: Partial<PeopleModel>){
constructor(payload?: Partial<PeopleModel>){
if (! payload ) { payload = {} ; }
this.Id = payload.Id || '';
this.First = payload.First || '';
this.Last = payload.Last || '';

+ 7
- 0
src/app/models/reward-ex-list-result.model.ts View File

@@ -0,0 +1,7 @@
import {GridDataResult} from '@progress/kendo-angular-grid';
import {RewardExModel} from './reward-ex.model';

export class RewardExListResultModel implements GridDataResult {
public data: RewardExModel[];
public total: number;
}

+ 72
- 10
src/app/models/reward-ex.model.ts View File

@@ -1,6 +1,5 @@
import {LoanModel} from './loan.model';
import {PayOutModel} from './payout.ex.model';
import {PeopleModel} from './people.model';
import {PayOutExModel} from './payout.ex.model';
import {UserExModel} from './userExModel';

export class RewardExModel {
@@ -13,12 +12,33 @@ export class RewardExModel {
public FromId: string;
public PayOutId: number;

public To?: UserExModel;
public Beneficiary?: UserExModel;
public Loan?: LoanModel;
public From?: UserExModel;
public PayOut?: PayOutModel;

constructor(payload: Partial<RewardExModel>) {
// derived content, for compatible with reward-by-user purpose
public ToFirst: string;
public ToMiddle: string;
public ToLast: string;
public ToTitle: string;
public ToDisplay: string;
public ToNick: string;

public FromFirst: string;
public FromMiddle: string;
public FromLast: string;
public FromTitle: string;
public FromDisplay: string;
public FromNick: string;

// Loan related derived property
public Item: string;
public Status: string;
public Settlement: Date;

constructor(payload?: Partial<RewardExModel>) {
if ( !payload ) { payload = {}; }

this.Id = payload.Id || 0;
if ( payload.Ts ) {
this.Ts = new Date(payload.Ts);
@@ -32,21 +52,63 @@ export class RewardExModel {
this.FromId = payload.FromId || '';
this.PayOutId = payload.PayOutId || 0;

if (payload.To) {
this.To = new UserExModel(payload.To);
// derived content
// this.ToFirst = payload.ToFirst || '';
// this.ToMiddle = payload.ToMiddle || '';
// this.ToLast = payload.ToLast || '';
// this.ToTitle = payload.ToTitle || '';
// this.ToDisplay = payload.ToDisplay || '';
// this.ToNick = payload.ToNick || '';
// this.Item = payload.Item || '';
// this.Status = payload.Status || '';
// this.Settlement = new Date(payload.Settlement);

if (payload.Beneficiary) {
this.Beneficiary = new UserExModel(payload.Beneficiary);
this.ToFirst = this.Beneficiary.First || '';
this.ToMiddle = this.Beneficiary.Middle || '';
this.ToLast = this.Beneficiary.Last || '';
this.ToTitle = this.Beneficiary.Title || '';
this.ToDisplay = this.Beneficiary.Display || '';
this.ToNick = this.Beneficiary.Nick || '';
}

if (payload.Loan) {
this.Loan = new LoanModel(payload.Loan);
this.Item = this.Loan.Item || '';
this.Status = this.Loan.Status || '';
if ( this.Loan.Settlement ){
this.Settlement = new Date(payload.Settlement);
}else{
this.Settlement = new Date('1900-01-01');
}
}

if ( payload.From) {
this.From = new UserExModel(payload.From);
this.FromFirst = this.From.First || '';
this.FromMiddle = this.From.Middle || '';
this.FromLast = this.From.Last || '';
this.FromTitle = this.From.Title || '';
this.FromDisplay = this.From.Display || '';
this.FromNick = this.From.Nick || '';
}

if ( payload. PayOut ) {
this.PayOut = new PayOutModel(payload.PayOut);
}
}

get Paid(): boolean{
return this.PayOutId > 0 ;
}

public Equal(v: RewardExModel): boolean{
if ( this.Id !== v.Id) { return false; }
if ( this.Ts !== v.Ts ) { return false; }
if ( this.ToId !== v.ToId) { return false; }
if ( this.Amount !== v.Amount ) { return false; }
if ( this.Description !== v.Description ) { return false; }
if ( this.LoanId !== v.LoanId ) { return false; }
if ( this.FromId !== v.FromId ) { return false; }
if ( this.PayOutId !== v.PayOutId ) { return false; }
return true;
}
}

+ 13
- 13
src/app/models/reward.model.ts View File

@@ -7,35 +7,35 @@ export class RewardModel {
public Id: number,
public LoanId: string,
public PayOutId: number,
public To: string,
public From: string,
public ToId: string,
public FromId: string,
public Ts: Date,
private lc: LoanModelCallBacks
){}


public get Role(): string {
return this.lc.getUserRole(this.To);
return this.lc.getUserRole(this.ToId);
}

public get UserId(): string {
return this.To;
}
public set UserId(v) {
this.To = v;
}
// public get UserId(): string {
// return this.ToId;
// }
//
// public set UserId(v) {
// this.ToId = v;
// }

public get UserName(): string {
return this.lc.getUserName(this.To);
return this.lc.getUserName(this.ToId);
}

public get photoUrlTo(): string{
return this.lc.getUserPhotoUrl(this.To);
return this.lc.getUserPhotoUrl(this.ToId);
}

public get photoUrlFrom(): string{
return this.lc.getUserPhotoUrl(this.From);
return this.lc.getUserPhotoUrl(this.FromId);
}
}


+ 2
- 1
src/app/models/userExModel.ts View File

@@ -15,8 +15,9 @@ export class UserExModel extends PeopleModel{
Role: UserRoles;
Broker?: BrokerModel;
Login: string;
constructor(payload: Partial<UserExModel>) {
constructor(payload?: Partial<UserExModel>) {
super(payload);
if ( ! payload ) { payload = {}; }
this.Role = payload.Role || 'People' as UserRoles;
this.Login = payload.Login || '';
this.Broker = new BrokerModel(payload);

+ 12
- 7
src/app/pay-in/pay-in.component.ts View File

@@ -453,15 +453,17 @@ export class PayInComponent implements OnInit {

private refreshFilterByUpload(): void{
// make a copy of existing filters
const newFilter = { logic: 'and', filters: [], ...this.state.Filter.filters};
const newFilter = { logic: 'and', filters: []};

// remove any existing filters about UploadsId
newFilter.filters = newFilter.filters.filter(v => {
if (!( 'field' in v) && v.operator === 'or') { // could be LoanId or UploadsIds
const subFilters = v.filters;
return ! subFilters.every( subf => subf.field === 'UploadIds' ); // if sub-filters are all about UploadsIds, we remove it
this.state.Filter.filters.forEach(v => {
if ('field' in v && 'operator' in v && 'value' in v) {
if ( v.field !== 'UploadIds' ){
newFilter.filters.push (v);
}
}else{
newFilter.filters.push (v);
}
return true;
});

// build new filters
@@ -475,7 +477,10 @@ export class PayInComponent implements OnInit {
});

// add new filter
newFilter.filters.push( uploadsFilter as CompositeFilterDescriptor );
if (uploadsFilter.filters.length > 0){
newFilter.filters.push( uploadsFilter as CompositeFilterDescriptor );
}

this.state.Filter = newFilter as CompositeFilterDescriptor;

}

+ 92
- 28
src/app/pay-out-details/pay-out-details.component.html View File

@@ -5,27 +5,57 @@
<div class="col-md-12">
<div class="card">
<div class="card-header bg-transparent header-elements-inline">
<app-reward-select></app-reward-select>
<div class="header-elements">
<button> test </button>
<kendo-chip
[removable]="true"
(remove)="onRemoveChip('somelabel', $event)"
>
<span class="k-avatar-image invoice-chip" [ngStyle]="{'background-image': background}"></span>
<span> approved</span>
<kendo-chip *ngIf="model.Prepared.Id !== '' "
[removable]="model.Approved.Id === ''"
(remove)="onCancelPrepared()" >
<span class="k-avatar-image invoice-chip"
[ngStyle]="{'background-image': photoBackground(model.Prepared.Id)}"></span>
<span> prepared </span> by <span> {{model.Prepared.Display}}</span>
</kendo-chip>

<kendo-chip
[removable]="false"
(remove)="onRemoveChip('somelabel', $event)"
>
<span class="k-avatar-image invoice-chip" [ngStyle]="{'background-image': background}"></span>
<span> approved</span>
<kendo-chip *ngIf="model.Approved.Id !== '' " [removable]="model.Paid.Id === ''" (remove)="onRevoke()" >
<span class="k-avatar-image invoice-chip"
[ngStyle]="{'background-image': photoBackground(model.Approved.Id)}"></span>
<span> approved </span> by <span> {{model.Approved.Display}}</span>
</kendo-chip>

<kendo-chip *ngIf="model.Paid.Id !== '' " [removable]="model.Paid.Id !== ''" (remove)="onUnPaid()" >
<span class="k-avatar-image invoice-chip"
[ngStyle]="{'background-image': photoBackground(model.Paid.Id)}"></span>
<span> paid </span> by <span> {{model.Paid.Display}}</span>
</kendo-chip>

<button *ngIf="model.Prepared.Id !== '' && model.Approved.Id === ''"
type="button" class="btn btn-primary" (click)="onApprove()">
<b><i class="fa fa-paper-plane-o mr-1"></i></b> Approve
</button>


<div class="select-beneficiary" *ngIf="model.Prepared.Id === ''">
<app-people-select #selectPeople [placeholder]= "'choose pay to'"
[(ngModel)]="model.To"
(ngModelChange)="onToChange($event)">
</app-people-select>
</div>

<button *ngIf="model.Prepared.Id === '' && model.Approved.Id === '' && model.Rewards.length > 1"
type="button" class="btn btn-primary" (click)="onPrepared()">
<b><i class="fa fa-paper-plane-o mr-1"></i></b> Finish
</button>

<div class="make-payment" *ngIf="model.Prepared.Id !== '' && model.Approved.Id !== '' && model.Paid.Id === ''">
<kendo-numerictextbox [(ngModel)]="model.PayAmount"> </kendo-numerictextbox>
<kendo-datepicker [(ngModel)]="model.PayDate"> </kendo-datepicker>
<button type="button" class="btn btn-primary" (click)="onPaid()">
<b><i class="fa fa-paper-plane-o mr-1"></i></b> Paid
</button>

</div>

</div>
</div>
<kendo-pdf-export #pdf paperSize="A4" margin="0.5cm">
<kendo-pdf-export #pdf paperSize="A4" [scale]="0.6" margin="0.2cm">
<div class="card-body">
<div class="row">
<div class="col-sm-6">
@@ -44,22 +74,33 @@
<div class="col-sm-6">
<div class="mb-4 ">
<div class="text-sm-right">
<h4 class="invoice-color mb-2 mt-md-2">Invoice #BBB1243</h4>
<h4 class="invoice-color mb-2 mt-md-2">Invoice #{{model.Id }}</h4>
<table class="invoice-to-details">
<tr (click)="onEditBeneficiary()">
<td class="left">To</td><td class="middle">:</td><td class="right"><h5 class="my-2">
{{ model.To.Display }}</h5></td>
</tr>
<tr>
<td class="left">To</td><td class="middle">:</td><td class="right"><h5 class="my-2">Tibco Turang</h5></td>
<td class="left">email</td><td class="middle">:</td><td class="right">
{{ model.To.Login}} </td>
</tr>
<tr>
<td class="left">email</td><td class="middle">:</td><td class="right">abc@hotmail.com</td>
<td class="left">Date</td><td class="middle">:</td><td class="right">
<span *ngIf="model.Ts.getFullYear() > 1900"> {{ model.Ts | date: "yyyy-MM-dd" }} </span>
</td>
</tr>
<tr>
<td class="left">Date</td><td class="middle">:</td><td class="right">March 15, 2020</td>
<td class="left">Pay Date</td><td class="middle">:</td><td class="right">
<span *ngIf="model.PayDate.getFullYear() > 1900"> {{model.PayDate | date: "yyyy-MM-dd" }}
</span></td>
</tr>
<tr>
<td class="left">Pay Date</td><td class="middle">:</td><td class="right">March 15, 2020</td>
<td class="left">Paid Amount</td><td class="middle">:</td>
<td class="right"> {{model.PayAmount | currency}}</td>
</tr>
<tr>
<td class="left">Paid To</td><td class="middle">:</td><td class="right">BSB/ACC</td>
<td class="left">Status</td><td class="middle">:</td>
<td class="right"> <h5 class="status"> {{model.Status | uppercase}} </h5> </td>
</tr>

</table>
@@ -68,8 +109,21 @@
</div>
</div>
</div>
<div #anchorSelectReward class="anchor"></div>
<app-reward-select *ngIf="model.Prepared.Id === ''"
[selectedRewards]="rewards"
[showNonPaidOnly]="true"
(valueChange)="onChangeRewardList($event)">
</app-reward-select>
<kendo-popup class="hint" [anchor]="anchorSelectReward" *ngIf="hintSelectRewardsButton"
[animate]="false"
[margin]="{ horizontal: -20, vertical: -10 }">
<kendo-icon [name]="'cursor'" [size]="'large'" [themeColor]="'warning'"></kendo-icon>
</kendo-popup>
<div class="table-responsive">
<app-single-payout-rewards-list></app-single-payout-rewards-list>
<app-single-payout-rewards-list [items]="rewards">

</app-single-payout-rewards-list>
</div>
<div class="card-body">
<div class="d-md-flex flex-md-wrap">
@@ -80,16 +134,16 @@
<tbody>
<tr>
<th class="text-left">Subtotal:</th>
<td class="text-right">{{amount | currency}}</td>
<td class="text-right">{{subTotal | currency}}</td>
</tr>
<tr>
<th class="text-left">Tax: <span class="font-weight-normal">(25%)</span></th>
<td class="text-right">$27</td>
<th class="text-left">Tax: <span class="font-weight-normal">(10%)</span></th>
<td class="text-right">{{tax | currency}}</td>
</tr>
<tr>
<th class="text-left">Total:</th>
<td class="text-right text-primary">
<h5 class="font-weight-semibold">$1,160</h5>
<h5 class="font-weight-semibold">{{Total | currency}}</h5>
</td>
</tr>
</tbody>
@@ -99,8 +153,18 @@
</div>
</div>
</kendo-pdf-export>
<div class="text-right mt-3"> <button type="button" class="btn btn-primary" (click)="pdf.saveAs('invoice.pdf')"><b><i class="fa fa-paper-plane-o mr-1"></i></b> Send invoice</button> </div>
<div class="card-footer"> <span class="text-muted">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.Duis aute irure dolor in reprehenderit</span> </div>
<div class="text-right mt-3">
<button type="button" class="btn btn-primary" (click)="pdf.saveAs('invoice.pdf')">
<b><i class="fa fa-paper-plane-o mr-1"></i></b> Download invoice</button>
<button type="button" class="btn btn-primary" (click)="onGotoRewardList()">
<b><i class="fa fa-paper-plane-o mr-1"></i></b>Back to reward list</button>

</div>
<div class="card-footer">
<span class="text-muted">
While some lender can only provide residential home loans, At SuperCredit we have the experience and knoweldge in Residential, Commerical, Business & Development Finance. We will not only provide a lending solution for our customer but guide you through every single steps of the process.
</span>
</div>
</div>
</div>
</div>

+ 41
- 2
src/app/pay-out-details/pay-out-details.component.scss View File

@@ -1,3 +1,4 @@

div.InvoiceBody {
margin: 0;
font-family: Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
@@ -9,7 +10,7 @@ div.InvoiceBody {
background-color: darkgrey;
padding-top:20px;
padding-bottom:20px;
height:100%;
min-height:100%;

.container{
padding:0;
@@ -17,6 +18,44 @@ div.InvoiceBody {
kendo-chip {
margin-left: 10px;
}
button {
color: #fff;
background-color: #ca974d;
border-color: white;
padding: 3px 11px 3px 3px;
border-radius: 20px;
margin-left: 10px;
}
div.select-beneficiary{
width: 200px;
display: inline-block;
margin: 1px 1px 1px 10px;
border-left: 10px solid orange;
}

div.make-payment{
display: inline-block;
vertical-align: bottom;
margin-left:20px;
button {
margin-left: 15px;

}
}

div.anchor{
float: right;
background: yellow;
height: 10px;
right: 20px;
}

.hint {
animation: blink normal 0.5s infinite;
}
@keyframes blink { 50%{opacity:0} }


span.k-avatar-image.invoice-chip{
display:inline-block;
width:20px;
@@ -112,7 +151,7 @@ div.InvoiceBody {

@media (min-width: 768px) {
.wmin-md-400 {
min-width: 400px !important
max-width: 400px !important
}
}


+ 149
- 14
src/app/pay-out-details/pay-out-details.component.ts View File

@@ -1,31 +1,166 @@
import { Component, OnInit } from '@angular/core';
import {AfterViewInit, Component, Input, OnInit, ViewChild} from '@angular/core';
import {ChipRemoveEvent} from '@progress/kendo-angular-buttons';
import {RewardService} from '../service/reward.service';
import {RewardExModel} from '../models/reward-ex.model';
import {PayOutExModel} from '../models/payout.ex.model';
import {PayOutService} from '../service/payout.service';
import {PeopleSelectComponent} from '../people-select/people-select.component';
import {AppConfig} from '../app.config';
import {UserExModel} from '../models/userExModel';
import {SessionService} from '../service/session.service';
import {Router} from '@angular/router';

@Component({
selector: 'app-pay-out-details',
templateUrl: './pay-out-details.component.html',
styleUrls: ['./pay-out-details.component.scss']
})
export class PayOutDetailsComponent implements OnInit {
sales = [];
export class PayOutDetailsComponent implements OnInit , AfterViewInit{
@Input() Id = 0;
@Input() public model: PayOutExModel = new PayOutExModel();

amount = 3218;
background = 'url(\'https://svr2021.lawipac.com:8080/api/v1/avatar/0\')';
constructor() { }
@ViewChild('selectPeople', {static: false}) sp: PeopleSelectComponent;

subTotal = 0;
tax = 0;
Total = 0;

hintSelectRewardsButton = false;
hinted = false;

rewards: RewardExModel[] = [];
constructor(private rs: RewardService, private payOutService: PayOutService,
private cfg: AppConfig, private ss: SessionService, private router: Router) { }

ngOnInit(): void {
setInterval(() => {
this.amount += 1;
}, 1000);
if (this.Id > 0){
this.payOutService.getPayOutEx(this.Id).subscribe(
resp => {
this.model = new PayOutExModel(resp);
this.rewards = this.model.Rewards;
this.updateTotal(this.model.Rewards);
});
}
}

ngAfterViewInit(): void {
if ( this.Id === 0 ) {
this.sp.showDropDown();
}
}

public photoBackground(id: string): string {
return 'url(' + this.cfg.getUrl('avatar/' + id) + ')';
}

public onChangeRewardList( list: RewardExModel[]): void {
this.rewards = list;
this.model.Rewards = this.rewards;
this.payOutService.savePayOut(this.model.toPayOutModel()).subscribe( resp => {
this.model = new PayOutExModel(resp);
});
this.updateTotal(list);
}

private updateTotal(list: RewardExModel[]): void {
this.Total = 0;
list.forEach( v => {
this.Total += v.Amount;
});
this.subTotal = this.Total / 1.1;
this.tax = this.Total - this.subTotal;
}

public onEditBeneficiary(): void{
this.sp.showDropDown();
}
public onToChange(e: any): void {
if ( this.model.Rewards.length === 0 && this.hinted === false ){
this.hintSelectRewardsButton = true;
this.hinted = true;
setTimeout( () => {
this.hintSelectRewardsButton = false;
}, 2000);
}
}

public onPrepared(): void {
this.model.Prepared.Id = this.ss.CurrentUser.Id;
this.payOutService.setPayOutPrepared(this.model.toPayOutModel()).subscribe(
resp => {
this.model.Prepared = new UserExModel(resp.Prepared);
this.model.Status = resp.Status;
}
);
}

public onCancelPrepared(): void {
this.model.Prepared.Id = '';
this.payOutService.setPayOutUnPrepared(this.model.toPayOutModel()).subscribe(
resp => {
this.model.Prepared = new UserExModel(resp.Prepared);
this.model.Status = resp.Status;
}
);
}

public onApprove(): void{
this.model.Approved.Id = this.ss.CurrentUser.Id;
this.payOutService.setPayOutApproved(this.model.toPayOutModel()).subscribe(
resp => {
this.model.Approved = new UserExModel(resp.Approved);
this.model.Status = resp.Status;

// prepare payment
if ( this.model.PayDate.getFullYear() <= 1900 ){
this.model.PayDate = new Date();
}
if (this.model.PayAmount === 0 && this.Total !== 0){
this.model.PayAmount = this.subTotal;
}

}
);
}

public onRevoke(): void {
this.payOutService.setPayOutUnapproved(this.model.toPayOutModel()).subscribe(
resp => {
this.model.Approved = new UserExModel(resp.Approved);
this.model.Status = resp.Status;

this.model.Paid.Id = '';
this.model.PayDate = new Date('1900-01-01');
this.model.PayAmount = 0;
}
);
}

public onPaid(): void {
this.model.Paid.Id = this.ss.CurrentUser.Id;
this.payOutService.setPayOutPaid(this.model.toPayOutModel()).subscribe(
resp => {
this.model.Paid = new UserExModel(resp.Paid);
this.model.Status = resp.Status;
this.model.PayDate = new Date(resp.PayDate);
this.model.PayAmount = resp.PayAmount;
}
);
}

range(start: number, end: number): number[] {
if (start === end) { return [start]; }
return [start, ...this.range(start + 1, end)];
public onUnPaid(): void{
this.payOutService.setPayOutUnpaid(this.model.toPayOutModel()).subscribe(
resp => {
this.model.Paid = new UserExModel(resp.Paid);
this.model.Status = resp.Status;
this.model.PayDate = new Date(resp.PayDate);
this.model.PayAmount = resp.PayAmount;
}
);
}

onRemoveChip(s: string, e: ChipRemoveEvent): void {
console.log(s, e);
public onGotoRewardList(): void{
this.router.navigate(['/list-all-rewards']).then();
}
}


+ 4
- 4
src/app/people-select/people-select.component.html View File

@@ -2,25 +2,25 @@
#list
[data]="searchResult"
[textField]="'Display'"
[valueField]="getEffectiveField()"
[valueField]="'Id'"
[valuePrimitive]="true"

[listHeight]="300"
placeholder="Type here to search"
[placeholder]="placeholder"
[suggest]="true"
[filterable]="true"
[clearButton]="true"
[allowCustom]="false"

[virtual]="{itemHeight: 36}"

[formControl]="formControl"
(filterChange)="filterChange($event)"
(valueChange)="valueChange($event)"
(valueChange)="onSelectionChange($event)"
>
<kendo-combobox-column
[field]="'Display'"
[title]="'Contact Name'"
[width]="width"
>
<ng-template
kendoMultiColumnComboBoxColumnCellTemplate

+ 43
- 158
src/app/people-select/people-select.component.ts View File

@@ -1,11 +1,9 @@
import {Component, forwardRef, Input, OnInit, ViewChild} from '@angular/core';
import {Component, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild} from '@angular/core';
import {AuthService} from '../service/auth.service';
import { debounce } from 'ts-debounce';
import {PeopleModel} from '../models/people.model';
import {ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms';
import {MultiColumnComboBoxComponent} from '@progress/kendo-angular-dropdowns';
import {Observable} from 'rxjs';
import {PeopleService} from '../service/people.service';
import {UserExModel} from '../models/userExModel';

@Component({
selector: 'app-people-select',
@@ -22,61 +20,53 @@ import {PeopleService} from '../service/people.service';
export class PeopleSelectComponent implements OnInit, ControlValueAccessor {

@Input() disabled = false;
@Input() translateId: true; // always tralsnate user Id, because name is not unique
@Input() width: number;
@Input() initSearch: PeopleModel[];
@Input() placeholder: string;
@Input() searchWithin: UserExModel[] = [];
@Input() formControl: FormControl = new FormControl(); // this is a dummy place holder
@Output() valueChange: EventEmitter<UserExModel> = new EventEmitter<UserExModel>();
@ViewChild('list', {static: true}) public text: MultiColumnComboBoxComponent;

public searchResult: PeopleModel[] = [];
public searchResult: UserExModel[] = [];
public value = ''; // selecting the default and only empty contact element // TODO: remove ngModel
public total = 0;

private debounceFilter: any ;

// Function to call when the rating changes.
private onChange = ( nameOrId: string) => {};
private onChange = ( nameOrId: UserExModel) => {};
// Function to call when the input is touched (when a star is clicked).
private onTouched = () => {};


constructor(private auth: AuthService, private ps: PeopleService) { }

ngOnInit(): void {
this.prepareSearchPeople();
this.initSearchResult();
}

private initSearchResult(): void {
this.initSearch.forEach( v => {
this.searchResult.push(v);
});
this.total = this.initSearch.length;
}

private prepareSearchPeople(): void{
this.debounceFilter = debounce( (filter: string): void => {
this.ps.getPeopleList(filter).subscribe(
resp => {
this.text.loading = false;
this.searchResult = resp.List;
this.total = resp.Count;
},
error => { this.text.loading = false; },
() => { this.text.loading = false; }
);
}, 500) ;
this.loadFilteredPeopleList(''); // load every one
}

getEffectiveField(): string {
return this.translateId ? 'Id' : 'Display';
private loadFilteredPeopleList( filter: string): void {
this.text.loading = true;
this.ps.getUserExList(filter).subscribe(
resp => {
this.text.loading = false;
this.searchWithin = resp.List;
this.total = resp.Count;
this.searchResult = this.searchWithin;
},
error => { this.text.loading = false; },
() => { this.text.loading = false; }
);
}

filterChange(filter: string): void {
if ( filter.length > 0 ) {
this.text.loading = true;
this.debounceFilter(filter); // conduct search even if filter is empty
}
this.searchResult = [];
this.searchWithin.forEach( v => {
if ( filter === '' ){
this.searchResult.push(v);
} else if ( v.Id === filter) {
this.searchResult.push (v);
} else if ( v.Display.includes( filter ) ){
this.searchResult.push(v);
}
});
}

public getContactImageUrl(contactId: string): string {
@@ -84,133 +74,25 @@ export class PeopleSelectComponent implements OnInit, ControlValueAccessor {
}

// ComboBox emit event on value change
public valueChange(str: string): void {
return;
console.log('value change', str);
this.onChange(this.value);
}

// public selectionChange(value: PeopleModel): void {
// this.onTouched();
// console.log('selectionChange', value);
// this.value = this.translateId ? value.Id : value.FullName;
// this.onChange(this.value);
// }

// public open(): void {
// this.onTouched();
// // console.log('open', this.text);
// }
//
// public close(): void {
// this.onTouched();
// // console.log('close', this.text);
// }
//
// public focus(): void {
// this.onTouched();
// // console.log('focus', this.text);
// }
//
// public blur(): void {
// // console.log('blur', this.text);
// }

// Allows Angular to update the model (name or ID).
// Update the model and changes needed for the view here.
writeValue(nameOrId: string): void {
if ( nameOrId === undefined ){
console.log('who called me for write', this);
return;
}
const changed = nameOrId !== this.value;
if (this.needSearch(nameOrId) ){
this.text.loading = true;
console.log('searching ... ', nameOrId, this.translateId ? 'by id' : 'by name');
this.searchUser(nameOrId, this.translateId).subscribe(
ppl => {
const person = new PeopleModel(ppl);
console.log('got search result ', person);
this.searchResult.push(person); // make sure it's available for selection, thus proper display
this.value = this.translateId ? person.Id : person.FullName;
console.log('before update', this.text.value);
this.text.value = this.value;
console.log('after update', this.text.value);
}, error => { console.error(error); this.text.loading = false; },
() => {this.text.loading = false; }
);
if ( changed ) {
this.onChange(nameOrId);
}
}
}

needSearch(incoming: string ): boolean {
if ( incoming === undefined || incoming === '' || incoming === this.value ) {
return false;
public onSelectionChange(str: string): void {
const p = this.searchWithin.find( v => v.Id === str);
if ( p ){
this.onChange(p);
}

const idx = this.searchResult.findIndex( person => {
if ( this.translateId) {
return person.Id === incoming;
}else{
return person.FullName.includes(incoming);
}
});

return idx === -1; // not found need search
}

// Search a user either based on partial name or a complete ID
searchUser(nameOrId: string, translateId: boolean): Observable<PeopleModel>{
if ( translateId ) {
return this.ps.getPeopleById(nameOrId);
// return this.searchPersonById( nameOrId) ;
writeValue(inV: UserExModel): void {
console.log( ' value in', inV);
if (inV){
this.value = inV.Id;
}else{
// return this.ps.searchById(nameOrId);
// return this.searchPersonByName(nameOrId) ;
this.value = null;
}
}

// searchPersonByName(name: string): Observable<PeopleModel>{
// return new Observable ( observer => {
// const dummy: PeopleModel = new PeopleModel(
// 'dummy-id',
// 'FSearch',
// 'LResult',
// '',
// 'Mr.',
// 'Display Name',
// 'Nick Name'
// );
// setTimeout(() => {
// observer.next(dummy);
// observer.complete();
// }, 1000);
// });
// }
//
// searchPersonById( id: string): Observable<PeopleModel> {
// return new Observable ( observer => {
// const dummy: PeopleModel = new PeopleModel(
// id,
// 'FSearch',
// 'LResult',
// '',
// 'Mr.',
// 'P:' + id,
// 'Nick Name'
// );
// setTimeout(() => {
// observer.next(dummy);
// observer.complete();
// }, 1000);
// });
// }

// Allows Angular to register a function to call when the model (rating) changes.
// Save the function as a property to call later here.
registerOnChange(fn: (nameOrId: string) => void): void {
registerOnChange(fn: (nameOrId: UserExModel) => void): void {
this.onChange = fn;
}
// Allows Angular to register a function to call when the input has been touched.
@@ -223,4 +105,7 @@ export class PeopleSelectComponent implements OnInit, ControlValueAccessor {
this.disabled = isDisabled;
}

public showDropDown(): void{
this.text.toggle(true);
}
}

+ 1
- 1
src/app/reward-paid/reward-paid.component.html View File

@@ -1,4 +1,4 @@
<div class="box">
<app-pay-out-details></app-pay-out-details>
<app-pay-out-details [Id]="Id"></app-pay-out-details>
</div>


+ 4
- 3
src/app/reward-paid/reward-paid.component.ts View File

@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';

@Component({
selector: 'app-reward-paid',
@@ -6,11 +7,11 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./reward-paid.component.scss']
})
export class RewardPaidComponent implements OnInit {
constructor() { }
Id = 0;
constructor(private actRoute: ActivatedRoute) { }

ngOnInit(): void {
this.Id = this.actRoute.snapshot.params.id;
}


}

+ 5
- 1
src/app/reward-select/reward-select.component.html View File

@@ -8,6 +8,10 @@
(click)="onToggle()"
></button>
<div class='popup-content'>
<app-list-all-rewards></app-list-all-rewards>
<app-list-all-rewards (confirmSelection)="onConfirmSelection($event)"
[selectedRewards]="selectedRewards"
[showNonPaidOnly]="showNonPaidOnly"
>
</app-list-all-rewards>
</div>
</kendo-popup>

+ 25
- 2
src/app/reward-select/reward-select.component.ts View File

@@ -2,6 +2,7 @@ import {Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angula
import {ListAllLoansComponent} from '../list-all-loans/list-all-loans.component';
import {LoanModel} from '../models/loan.model';
import {RewardModel} from '../models/reward.model';
import {RewardExModel} from '../models/reward-ex.model';

@Component({
selector: 'app-reward-select',
@@ -12,8 +13,10 @@ export class RewardSelectComponent implements OnInit {
// @ViewChild('list', {static: false} ) list: list-all;
@Input() Max = 1; // we do not support multiple loan selection at the moment
@Input() readOnly = false;
@Output() valueChange: EventEmitter<RewardModel[]> = new EventEmitter<RewardModel[]>();
public Rewards: RewardModel[];
@Input() showNonPaidOnly = false;
@Output() valueChange: EventEmitter<RewardExModel[]> = new EventEmitter<RewardExModel[]>();
@Input() public selectedRewards: RewardExModel[] = [];


// popup
public showPopup = false;
@@ -27,4 +30,24 @@ export class RewardSelectComponent implements OnInit {
public onToggle(): void {
this.showPopup = !this.showPopup;
}

public onConfirmSelection(sel: RewardExModel[]): void {
if (this.isDiff(this.selectedRewards, sel)) {
this.valueChange.emit (sel);
this.selectedRewards = sel;
console.log('reward-select', this.selectedRewards);
}
this.showPopup = !this.showPopup;
}

private isDiff(a: RewardExModel[], b: RewardExModel[]): boolean {
if ( !a || !b ) { return a !== b; }
if ( a.length !== b.length) { return true; }
for ( let i = 0; i < a.length; i++ ){
if ( ! a[i].Equal(b[i]) ){
return true;
}
}
return false;
}
}

+ 30
- 1
src/app/service/payout.service.ts View File

@@ -4,16 +4,45 @@ import {AuthService} from './auth.service';
import {Observable} from 'rxjs';
import {PayOutExModel} from '../models/payout.ex.model';
import {CompositeFilterDescriptor} from '@progress/kendo-data-query';
import {PayoutModel} from '../models/payout.model';

@Injectable({providedIn: 'root'})
export class PayOutService {
constructor(private http: HttpClient, private auth: AuthService ){ }

public getPayOutEx(id: string): Observable<PayOutExModel> {
public getPayOutEx(id: number): Observable<PayOutExModel> {
return this.http.get<PayOutExModel>(this.auth.getUrl('payout-ex/' + id));
}

public getPayOutList(filter: CompositeFilterDescriptor): Observable<PayOutExModel[]> {
return this.http.post<PayOutExModel[]>(this.auth.getUrl('payout-ex-list/'), filter);
}

public savePayOut(po: PayoutModel): Observable<PayOutExModel>{
return this.http.post<PayOutExModel>(this.auth.getUrl('payout/'), po);
}

public setPayOutPrepared(po: PayoutModel): Observable<PayOutExModel>{
return this.http.post<PayOutExModel>(this.auth.getUrl('payout-prepared/'), po);
}

public setPayOutUnPrepared( po: PayoutModel): Observable<PayOutExModel>{
return this.http.post<PayOutExModel>(this.auth.getUrl('payout-unprepared/'), po);
}

public setPayOutApproved(po: PayoutModel): Observable<PayOutExModel>{
return this.http.post<PayOutExModel>(this.auth.getUrl('payout-approved/' ), po);
}

public setPayOutUnapproved(po: PayoutModel): Observable<PayOutExModel>{
return this.http.post<PayOutExModel>(this.auth.getUrl('payout-unapproved/'), po);
}

public setPayOutPaid( po: PayoutModel): Observable<PayOutExModel>{
return this.http.post<PayOutExModel>(this.auth.getUrl('payout-paid/' ), po);
}

public setPayOutUnpaid(po: PayoutModel): Observable<PayOutExModel>{
return this.http.post<PayOutExModel>(this.auth.getUrl('payout-unpaid/'), po);
}
}

+ 6
- 0
src/app/service/people.service.ts View File

@@ -7,6 +7,7 @@ import {BrokerModel} from '../models/broker.model';
import {LoanModel} from '../models/loan.model';
import {ChangePassword} from '../models/change-password.model';
import {UserExtraModel} from '../models/user-extra.model';
import {UserExModel} from '../models/userExModel';

@Injectable({providedIn: 'root'})
export class PeopleService {
@@ -24,6 +25,11 @@ export class PeopleService {
return this.http.get<{Count: number, List: PeopleModel[]}>(this.auth.getUrl( 'people-list/'), { params});
}

public getUserExList(filter: string): Observable<{Count: number, List: UserExModel[]}> {
const params = new HttpParams().set('filter', filter);
return this.http.get<{Count: number, List: UserExModel[]}>(this.auth.getUrl( 'user-ex-list/'), { params});
}

public getBrokerList(filter: string): Observable<{Count: number, List: BrokerModel[]}> {
const params = new HttpParams().set('filter', filter);
return this.http.get<{Count: number, List: BrokerModel[]}>(this.auth.getUrl( 'broker-list/'), { params});

+ 35
- 0
src/app/service/reward.service.ts View File

@@ -0,0 +1,35 @@
import {Injectable} from '@angular/core';
import {RewardExModel} from '../models/reward-ex.model';
import {Observable} from 'rxjs';
import {HttpClient} from '@angular/common/http';
import {AppConfig} from '../app.config';
import {DataStateChangeEvent} from '@progress/kendo-angular-grid';
import {RewardExListResultModel} from '../models/reward-ex-list-result.model';
import {State} from '@progress/kendo-data-query';

@Injectable({providedIn: 'root'})
export class RewardService {
constructor(private http: HttpClient, private cfg: AppConfig) {
}

public getRewardExListByPayoutId(payOutId: number): Observable<RewardExListResultModel>{
const state = {
skip: 0, take: 0, filter: {
logic: 'and',
filters: [
{field: 'PayOutId', operator: 'eq', value: payOutId}
]
}
};
return this.getRewardExList(state as State);
}

public getUserReward(userid?: string): Observable<RewardExModel[]>{
if (! userid ) { userid = ''; }
return this.http.get<RewardExModel[]>(this.cfg.getUrl('user-reward/' + userid));
}

public getRewardExList(state: State): Observable<RewardExListResultModel> {
return this.http.post<RewardExListResultModel>(this.cfg.getUrl('reward-ex-list/'), state);
}
}

+ 27
- 1
src/app/single-payout-rewards-list/single-payout-rewards-list.component.html View File

@@ -1,3 +1,29 @@
<kendo-grid [scrollable]="false">
<kendo-grid [resizable]="true"
[scrollable]="'none'"
[data]="items">
<kendo-grid-column field="ToDisplay" title="Beneficiary" width="100">
</kendo-grid-column>
<kendo-grid-column field="Description">
<ng-template kendoGridCellTemplate let-dataItem>
<div class="invoice-item-description">
<span class="label"> For: </span> <span class="beneficiary"> {{dataItem.Description }} </span>

<p class="description" *ngIf="dataItem.LoanId !== '' ">
<span class="label"> Regarding Loan: </span>
<span class="regarding"> {{dataItem.Item}}, </span>
</p>
<span class="label"> Issued on : </span>
<span class="issued-on-date">{{dataItem.Ts | date:"yyyy-MMMM-dd" }} </span>


<span *ngIf="dataItem.FromId !== '0' && dataItem.FromId !== '' " class="money-giver">
From: {{dataItem.From.Display}}
</span>
<sub > * SFM Reference: {{ dataItem.Id}} *</sub>
</div>

</ng-template>
</kendo-grid-column>
<kendo-grid-column field="Amount" format="{0:c}" width="200"></kendo-grid-column>
</kendo-grid>


+ 24
- 0
src/app/single-payout-rewards-list/single-payout-rewards-list.component.scss View File

@@ -0,0 +1,24 @@
div.invoice-item-description{
width: 100%;

p.description{
margin-bottom: 1px;
}
span.label {
font-family: "Courier New";
color: #ab2828
}
span.beneficiary{
font-weight: bold;
color: black;
}

span.regarding{
font-family: "Courier New";
}

span.issued-on-date {
font-family: "Courier New";
color: darkgrey;
}
}

+ 5
- 2
src/app/single-payout-rewards-list/single-payout-rewards-list.component.ts View File

@@ -1,4 +1,4 @@
import {Component, Input, OnInit} from '@angular/core';
import {Component, Input, OnChanges, OnInit} from '@angular/core';
import {RewardModel} from '../models/reward.model';

@Component({
@@ -6,12 +6,15 @@ import {RewardModel} from '../models/reward.model';
templateUrl: './single-payout-rewards-list.component.html',
styleUrls: ['./single-payout-rewards-list.component.scss']
})
export class SinglePayoutRewardsListComponent implements OnInit {
export class SinglePayoutRewardsListComponent implements OnInit, OnChanges {
@Input() items: RewardModel[] = [];
constructor() { }

ngOnInit(): void {
// this.items.push(new RewardModel({}));
}
ngOnChanges(changes): void {
// console.log(changes, this.items);
}

}

Loading…
Cancel
Save