Moved from tslint to eslint as tslint was depreciated.
Added prettier and also prettied all the typescript files using prettier ESLint is using the AirBnB rules which are the most strict to lint the files.
This commit is contained in:
1 parent
40e79ff949
commit
1350870f9e
545 files changed
+8455
-7036
No files matched your search
@@ -1,11 +1,11 @@
|
||||
import {inject, TestBed} from '@angular/core/testing';
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
|
||||
import {PaymentAccountsResolver} from './payment-accounts-resolver.service';
|
||||
import { PaymentAccountsResolver } from './payment-accounts-resolver.service';
|
||||
|
||||
describe('PaymentAccountsResolver', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [PaymentAccountsResolverService]
|
||||
providers: [PaymentAccountsResolverService],
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from '@angular/router';
|
||||
import {Observable} from 'rxjs/internal/Observable';
|
||||
import {Account} from '../core/account';
|
||||
import {AccountService} from '../core/account.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { Account } from '../core/account';
|
||||
import { AccountService } from '../core/account.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PaymentAccountsResolver implements Resolve<Account[]> {
|
||||
|
||||
constructor(private ser: AccountService) {
|
||||
}
|
||||
constructor(private ser: AccountService) {}
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Account[]> {
|
||||
return this.ser.paymentAutocomplete('');
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import {DataSource} from '@angular/cdk/collections';
|
||||
import {Observable} from 'rxjs';
|
||||
import {Journal} from '../core/voucher';
|
||||
|
||||
import { DataSource } from '@angular/cdk/collections';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Journal } from '../core/voucher';
|
||||
|
||||
export class PaymentDataSource extends DataSource<Journal> {
|
||||
|
||||
constructor(private data: Observable<Journal[]>) {
|
||||
super();
|
||||
}
|
||||
@@ -13,6 +11,5 @@ export class PaymentDataSource extends DataSource<Journal> {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
}
|
||||
disconnect() {}
|
||||
}
|
||||
@@ -1,26 +1,49 @@
|
||||
<h1 mat-dialog-title>Edit Payment Entry</h1>
|
||||
<div mat-dialog-content>
|
||||
<form [formGroup]="form">
|
||||
<div fxLayout="row" fxLayoutAlign="space-around start" fxLayout.lt-md="column" fxLayoutGap="20px"
|
||||
fxLayoutGap.lt-md="0px">
|
||||
<div
|
||||
fxLayout="row"
|
||||
fxLayoutAlign="space-around start"
|
||||
fxLayout.lt-md="column"
|
||||
fxLayoutGap="20px"
|
||||
fxLayoutGap.lt-md="0px"
|
||||
>
|
||||
<mat-form-field fxFlex="65">
|
||||
<input type="text" matInput placeholder="Account" #accountElement [matAutocomplete]="auto"
|
||||
formControlName="account" autocomplete="off">
|
||||
<input
|
||||
type="text"
|
||||
matInput
|
||||
placeholder="Account"
|
||||
#accountElement
|
||||
[matAutocomplete]="auto"
|
||||
formControlName="account"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<mat-hint *ngIf="accBal">
|
||||
Balance as on Date: <strong>{{accBal.date | currency:'INR' | accounting}}</strong> /
|
||||
Final balance: <strong>{{accBal.total | currency:'INR' | accounting}}</strong>
|
||||
Balance as on Date: <strong>{{ accBal.date | currency: 'INR' | accounting }}</strong> /
|
||||
Final balance: <strong>{{ accBal.total | currency: 'INR' | accounting }}</strong>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-autocomplete #auto="matAutocomplete" autoActiveFirstOption [displayWith]="displayAccount"
|
||||
(optionSelected)="accountSelected($event)">
|
||||
<mat-option *ngFor="let account of accounts | async" [value]="account">{{account.name}}</mat-option>
|
||||
<mat-autocomplete
|
||||
#auto="matAutocomplete"
|
||||
autoActiveFirstOption
|
||||
[displayWith]="displayAccount"
|
||||
(optionSelected)="accountSelected($event)"
|
||||
>
|
||||
<mat-option *ngFor="let account of accounts | async" [value]="account">{{
|
||||
account.name
|
||||
}}</mat-option>
|
||||
</mat-autocomplete>
|
||||
<mat-form-field fxFlex="35">
|
||||
<mat-label>Amount</mat-label>
|
||||
<span matPrefix>₹ </span>
|
||||
<input type="text" matInput placeholder="Amount" formControlName="amount" autocomplete="off">
|
||||
<input
|
||||
type="text"
|
||||
matInput
|
||||
placeholder="Amount"
|
||||
formControlName="amount"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -28,4 +51,3 @@
|
||||
<button mat-button [mat-dialog-close]="false" cdkFocusInitial>Cancel</button>
|
||||
<button mat-button (click)="accept()" color="primary">Ok</button>
|
||||
</div>
|
||||
|
||||
@@ -8,9 +8,8 @@ describe('PaymentDialogComponent', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ PaymentDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
declarations: [PaymentDialogComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import {Component, Inject, OnInit} from '@angular/core';
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import {debounceTime, distinctUntilChanged, map, startWith, switchMap} from 'rxjs/operators';
|
||||
import {Account} from '../core/account';
|
||||
import {AccountService} from '../core/account.service';
|
||||
import {FormBuilder, FormGroup} from '@angular/forms';
|
||||
import {Observable, of as observableOf} from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators';
|
||||
import { Account } from '../core/account';
|
||||
import { AccountService } from '../core/account.service';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-payment-dialog',
|
||||
templateUrl: './payment-dialog.component.html',
|
||||
styleUrls: ['./payment-dialog.component.css']
|
||||
styleUrls: ['./payment-dialog.component.css'],
|
||||
})
|
||||
export class PaymentDialogComponent implements OnInit {
|
||||
accounts: Observable<Account[]>;
|
||||
@@ -22,7 +22,8 @@ export class PaymentDialogComponent implements OnInit {
|
||||
public dialogRef: MatDialogRef<PaymentDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
private fb: FormBuilder,
|
||||
private accountSer: AccountService) {
|
||||
private accountSer: AccountService,
|
||||
) {
|
||||
this.createForm();
|
||||
this.setupAccountAutocomplete();
|
||||
}
|
||||
@@ -30,7 +31,7 @@ export class PaymentDialogComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
this.form.setValue({
|
||||
account: this.data.journal.account,
|
||||
amount: this.data.journal.amount
|
||||
amount: this.data.journal.amount,
|
||||
});
|
||||
this.account = this.data.journal.account;
|
||||
}
|
||||
@@ -38,22 +39,20 @@ export class PaymentDialogComponent implements OnInit {
|
||||
createForm() {
|
||||
this.form = this.fb.group({
|
||||
account: '',
|
||||
amount: ''
|
||||
amount: '',
|
||||
});
|
||||
this.accBal = null;
|
||||
}
|
||||
|
||||
setupAccountAutocomplete(): void {
|
||||
const control = this.form.get('account');
|
||||
this.accounts = control.valueChanges
|
||||
.pipe(
|
||||
startWith(null),
|
||||
map(x => (x !== null && x.length >= 1) ? x : null),
|
||||
debounceTime(150),
|
||||
distinctUntilChanged(),
|
||||
switchMap(x => (x === null) ? observableOf([]) : this.accountSer.autocomplete(x))
|
||||
);
|
||||
|
||||
this.accounts = control.valueChanges.pipe(
|
||||
startWith(null),
|
||||
map((x) => (x !== null && x.length >= 1 ? x : null)),
|
||||
debounceTime(150),
|
||||
distinctUntilChanged(),
|
||||
switchMap((x) => (x === null ? observableOf([]) : this.accountSer.autocomplete(x))),
|
||||
);
|
||||
}
|
||||
|
||||
displayAccount(account?: Account): string | undefined {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {inject, TestBed} from '@angular/core/testing';
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
|
||||
import {PaymentResolver} from './payment-resolver.service';
|
||||
import { PaymentResolver } from './payment-resolver.service';
|
||||
|
||||
describe('PaymentResolver', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [PaymentResolver]
|
||||
providers: [PaymentResolver],
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from '@angular/router';
|
||||
import {Observable} from 'rxjs/internal/Observable';
|
||||
import {Voucher} from '../core/voucher';
|
||||
import {VoucherService} from '../core/voucher.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { Voucher } from '../core/voucher';
|
||||
import { VoucherService } from '../core/voucher.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PaymentResolver implements Resolve<Voucher> {
|
||||
|
||||
constructor(private ser: VoucherService) {
|
||||
}
|
||||
constructor(private ser: VoucherService) {}
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Voucher> {
|
||||
const id = route.paramMap.get('id');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {PaymentRoutingModule} from './payment-routing.module';
|
||||
import { PaymentRoutingModule } from './payment-routing.module';
|
||||
|
||||
describe('PaymentRoutingModule', () => {
|
||||
let paymentRoutingModule: PaymentRoutingModule;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {PaymentResolver} from './payment-resolver.service';
|
||||
import {AuthGuard} from '../auth/auth-guard.service';
|
||||
import {PaymentComponent} from './payment.component';
|
||||
import {PaymentAccountsResolver} from './payment-accounts-resolver.service';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { PaymentResolver } from './payment-resolver.service';
|
||||
import { AuthGuard } from '../auth/auth-guard.service';
|
||||
import { PaymentComponent } from './payment.component';
|
||||
import { PaymentAccountsResolver } from './payment-accounts-resolver.service';
|
||||
|
||||
const paymentRoutes: Routes = [
|
||||
{
|
||||
@@ -12,41 +12,32 @@ const paymentRoutes: Routes = [
|
||||
component: PaymentComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: {
|
||||
permission: 'journal'
|
||||
permission: 'journal',
|
||||
},
|
||||
resolve: {
|
||||
paymentAccounts: PaymentAccountsResolver,
|
||||
voucher: PaymentResolver
|
||||
voucher: PaymentResolver,
|
||||
},
|
||||
runGuardsAndResolvers: 'paramsChange'
|
||||
runGuardsAndResolvers: 'paramsChange',
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: PaymentComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: {
|
||||
permission: 'journal'
|
||||
permission: 'journal',
|
||||
},
|
||||
resolve: {
|
||||
paymentAccounts: PaymentAccountsResolver,
|
||||
voucher: PaymentResolver
|
||||
voucher: PaymentResolver,
|
||||
},
|
||||
runGuardsAndResolvers: 'paramsChange'
|
||||
}
|
||||
runGuardsAndResolvers: 'paramsChange',
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild(paymentRoutes)
|
||||
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
],
|
||||
providers: [
|
||||
PaymentResolver
|
||||
]
|
||||
imports: [CommonModule, RouterModule.forChild(paymentRoutes)],
|
||||
exports: [RouterModule],
|
||||
providers: [PaymentResolver],
|
||||
})
|
||||
export class PaymentRoutingModule {
|
||||
}
|
||||
export class PaymentRoutingModule {}
|
||||
@@ -4,11 +4,11 @@
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: #fff3cd
|
||||
background: #fff3cd;
|
||||
}
|
||||
|
||||
.unposted {
|
||||
background: #f8d7da
|
||||
background: #f8d7da;
|
||||
}
|
||||
|
||||
.center {
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
<mat-card>
|
||||
<mat-card-title-group>
|
||||
<mat-card-title>Payment</mat-card-title>
|
||||
<mat-icon matSuffix (click)="voucher.isStarred = !voucher.isStarred" class="pointer"
|
||||
[class.gold]="voucher.isStarred">
|
||||
<mat-icon
|
||||
matSuffix
|
||||
(click)="voucher.isStarred = !voucher.isStarred"
|
||||
class="pointer"
|
||||
[class.gold]="voucher.isStarred"
|
||||
>
|
||||
{{ voucher.isStarred ? 'star' : 'star_border' }}
|
||||
</mat-icon>
|
||||
</mat-card-title-group>
|
||||
<mat-card-content>
|
||||
<form [formGroup]="form" fxLayout="column">
|
||||
<div fxLayout="row" fxLayoutAlign="space-around start" fxLayout.lt-md="column" fxLayoutGap="20px"
|
||||
fxLayoutGap.lt-md="0px">
|
||||
<div
|
||||
fxLayout="row"
|
||||
fxLayoutAlign="space-around start"
|
||||
fxLayout.lt-md="column"
|
||||
fxLayoutGap="20px"
|
||||
fxLayoutGap.lt-md="0px"
|
||||
>
|
||||
<mat-form-field fxFlex="40">
|
||||
<input matInput [matDatepicker]="date" placeholder="Date" formControlName="date" autocomplete="off"
|
||||
#dateElement (focus)="dateElement.select()">
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="date"
|
||||
placeholder="Date"
|
||||
formControlName="date"
|
||||
autocomplete="off"
|
||||
#dateElement
|
||||
(focus)="dateElement.select()"
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="date"></mat-datepicker-toggle>
|
||||
<mat-datepicker #date></mat-datepicker>
|
||||
</mat-form-field>
|
||||
@@ -26,44 +42,70 @@
|
||||
<mat-form-field fxFlex="20">
|
||||
<mat-label>Payment Amount</mat-label>
|
||||
<span matPrefix>₹ </span>
|
||||
<input type="text" matInput formControlName="paymentAmount">
|
||||
<input type="text" matInput formControlName="paymentAmount" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div formGroupName="addRow" fxLayout="row" fxLayoutAlign="space-around start" fxLayout.lt-md="column"
|
||||
fxLayoutGap="20px" fxLayoutGap.lt-md="0px">
|
||||
<div
|
||||
formGroupName="addRow"
|
||||
fxLayout="row"
|
||||
fxLayoutAlign="space-around start"
|
||||
fxLayout.lt-md="column"
|
||||
fxLayoutGap="20px"
|
||||
fxLayoutGap.lt-md="0px"
|
||||
>
|
||||
<mat-form-field fxFlex="55">
|
||||
<input type="text" matInput placeholder="Account" #accountElement [matAutocomplete]="auto"
|
||||
formControlName="account" autocomplete="off">
|
||||
<input
|
||||
type="text"
|
||||
matInput
|
||||
placeholder="Account"
|
||||
#accountElement
|
||||
[matAutocomplete]="auto"
|
||||
formControlName="account"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<mat-hint *ngIf="accBal">
|
||||
Balance as on Date: <strong>{{accBal.date | currency:'INR' | accounting}}</strong> /
|
||||
Final balance: <strong>{{accBal.total | currency:'INR' | accounting}}</strong>
|
||||
Balance as on Date: <strong>{{ accBal.date | currency: 'INR' | accounting }}</strong> /
|
||||
Final balance: <strong>{{ accBal.total | currency: 'INR' | accounting }}</strong>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-autocomplete #auto="matAutocomplete" autoActiveFirstOption [displayWith]="displayAccount"
|
||||
(optionSelected)="accountSelected($event)">
|
||||
<mat-option *ngFor="let account of accounts | async" [value]="account">{{account.name}}</mat-option>
|
||||
<mat-autocomplete
|
||||
#auto="matAutocomplete"
|
||||
autoActiveFirstOption
|
||||
[displayWith]="displayAccount"
|
||||
(optionSelected)="accountSelected($event)"
|
||||
>
|
||||
<mat-option *ngFor="let account of accounts | async" [value]="account">{{
|
||||
account.name
|
||||
}}</mat-option>
|
||||
</mat-autocomplete>
|
||||
<mat-form-field fxFlex="25">
|
||||
<mat-label>Amount</mat-label>
|
||||
<span matPrefix>₹ </span>
|
||||
<input type="text" matInput placeholder="Amount" formControlName="amount" autocomplete="off">
|
||||
<input
|
||||
type="text"
|
||||
matInput
|
||||
placeholder="Amount"
|
||||
formControlName="amount"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-raised-button color="primary" (click)="addRow()" fxFlex="20">Add</button>
|
||||
</div>
|
||||
<mat-table #table [dataSource]="dataSource" matSort aria-label="Elements">
|
||||
|
||||
<!-- Account Column -->
|
||||
<ng-container matColumnDef="account">
|
||||
<mat-header-cell *matHeaderCellDef>Account</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">{{row.account.name}}</mat-cell>
|
||||
<mat-cell *matCellDef="let row">{{ row.account.name }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Amount Column -->
|
||||
<ng-container matColumnDef="amount">
|
||||
<mat-header-cell *matHeaderCellDef class="right">Amount</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" class="right">{{row.amount | currency:'INR'}}</mat-cell>
|
||||
<mat-cell *matCellDef="let row" class="right">{{
|
||||
row.amount | currency: 'INR'
|
||||
}}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Action Column -->
|
||||
@@ -80,16 +122,21 @@
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||
</mat-table>
|
||||
<mat-form-field>
|
||||
<mat-label>Narration</mat-label>
|
||||
<textarea matInput matTextareaAutosize matAutosizeMinRows="5" placeholder="Narration"
|
||||
formControlName="narration"></textarea>
|
||||
<textarea
|
||||
matInput
|
||||
matTextareaAutosize
|
||||
matAutosizeMinRows="5"
|
||||
placeholder="Narration"
|
||||
formControlName="narration"
|
||||
></textarea>
|
||||
</mat-form-field>
|
||||
<div fxLayout="row" fxLayoutGap="0.5%" fxLayoutAlign="center">
|
||||
<div class="img-container" fxFlex="20%" *ngFor="let item of voucher.files">
|
||||
<img [src]="item.thumbnail" (click)="zoomImage(item)">
|
||||
<img [src]="item.thumbnail" (click)="zoomImage(item)" />
|
||||
<button mat-icon-button class="overlay" (click)="deleteImage(item)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
@@ -97,29 +144,47 @@
|
||||
<div class="img-container" fxFlex="20%">
|
||||
<label ng-href for="fileUp">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTUwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjE1MCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjUwIiB5PSI3NSIgc3R5bGU9ImZpbGw6I2FhYTtmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMDBweDtmb250LWZhbWlseTpBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjtkb21pbmFudC1iYXNlbGluZTpjZW50cmFsIj4rPC90ZXh0Pjwvc3ZnPg==">
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTUwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjE1MCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjUwIiB5PSI3NSIgc3R5bGU9ImZpbGw6I2FhYTtmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMDBweDtmb250LWZhbWlseTpBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjtkb21pbmFudC1iYXNlbGluZTpjZW50cmFsIj4rPC90ZXh0Pjwvc3ZnPg=="
|
||||
/>
|
||||
</label>
|
||||
<input type="file" id="fileUp" multiple accept="image/*" style="display: none;"
|
||||
(change)="detectFiles($event)">
|
||||
<input
|
||||
type="file"
|
||||
id="fileUp"
|
||||
multiple
|
||||
accept="image/*"
|
||||
style="display: none"
|
||||
(change)="detectFiles($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-raised-button color="primary" (click)="save()" [disabled]="!canSave()">
|
||||
{{(voucher.id) ? 'Update' : 'Save'}}
|
||||
{{ voucher.id ? 'Update' : 'Save' }}
|
||||
</button>
|
||||
<button mat-raised-button (click)="post()" *ngIf="voucher.id"
|
||||
[disabled]="voucher.posted || auth.user.perms.indexOf('post-vouchers') === -1">
|
||||
{{(voucher.posted) ? 'Posted' : 'Post'}}
|
||||
<button
|
||||
mat-raised-button
|
||||
(click)="post()"
|
||||
*ngIf="voucher.id"
|
||||
[disabled]="voucher.posted || auth.user.perms.indexOf('post-vouchers') === -1"
|
||||
>
|
||||
{{ voucher.posted ? 'Posted' : 'Post' }}
|
||||
</button>
|
||||
<button mat-raised-button color="warn" (click)="confirmDelete()" *ngIf="voucher.id" [disabled]="!canSave()">
|
||||
<button
|
||||
mat-raised-button
|
||||
color="warn"
|
||||
(click)="confirmDelete()"
|
||||
*ngIf="voucher.id"
|
||||
[disabled]="!canSave()"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
<mat-card-subtitle *ngIf="voucher.id">
|
||||
Created on <strong>{{voucher.creationDate | localTime}}</strong> and
|
||||
Last Edited on <strong>{{voucher.lastEditDate | localTime}}</strong>
|
||||
by <strong>{{voucher.user.name}}</strong>. {{(voucher.poster) ? 'Posted by ' + voucher.poster : ''}}
|
||||
Created on <strong>{{ voucher.creationDate | localTime }}</strong> and Last Edited on
|
||||
<strong>{{ voucher.lastEditDate | localTime }}</strong> by
|
||||
<strong>{{ voucher.user.name }}</strong
|
||||
>. {{ voucher.poster ? 'Posted by ' + voucher.poster : '' }}
|
||||
</mat-card-subtitle>
|
||||
</mat-card>
|
||||
@@ -1,6 +1,6 @@
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import {PaymentComponent} from './payment.component';
|
||||
import { PaymentComponent } from './payment.component';
|
||||
|
||||
describe('PaymentComponent', () => {
|
||||
let component: PaymentComponent;
|
||||
@@ -8,9 +8,8 @@ describe('PaymentComponent', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [PaymentComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
declarations: [PaymentComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -22,7 +22,7 @@ import { Hotkey, HotkeysService } from 'angular2-hotkeys';
|
||||
@Component({
|
||||
selector: 'app-payment',
|
||||
templateUrl: './payment.component.html',
|
||||
styleUrls: ['./payment.component.css']
|
||||
styleUrls: ['./payment.component.css'],
|
||||
})
|
||||
export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
@ViewChild('accountElement', { static: true }) accountElement: ElementRef;
|
||||
@@ -49,7 +49,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private toaster: ToasterService,
|
||||
private auth: AuthService,
|
||||
private ser: VoucherService,
|
||||
private accountSer: AccountService
|
||||
private accountSer: AccountService,
|
||||
) {
|
||||
this.account = null;
|
||||
this.createForm();
|
||||
@@ -58,27 +58,47 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.data
|
||||
.subscribe((data: { voucher: Voucher, paymentAccounts: Account[] }) => {
|
||||
this.paymentAccounts = data.paymentAccounts;
|
||||
this.loadVoucher(data.voucher);
|
||||
});
|
||||
this.hotkeys.add(new Hotkey('f2', (event: KeyboardEvent): boolean => {
|
||||
setTimeout(() => {
|
||||
this.dateElement.nativeElement.focus();
|
||||
}, 0);
|
||||
return false; // Prevent bubbling
|
||||
}, ['INPUT', 'SELECT', 'TEXTAREA']));
|
||||
this.hotkeys.add(new Hotkey('ctrl+s', (event: KeyboardEvent): boolean => {
|
||||
if (this.canSave())
|
||||
this.save();
|
||||
return false; // Prevent bubbling
|
||||
}, ['INPUT', 'SELECT', 'TEXTAREA']));
|
||||
this.hotkeys.add(new Hotkey('ctrl+p', (event: KeyboardEvent): boolean => {
|
||||
if (this.voucher.id && !this.voucher.posted && this.auth.user.perms.indexOf('post-vouchers') !== -1)
|
||||
this.post();
|
||||
return false; // Prevent bubbling
|
||||
}, ['INPUT', 'SELECT', 'TEXTAREA']));
|
||||
this.route.data.subscribe((data: { voucher: Voucher; paymentAccounts: Account[] }) => {
|
||||
this.paymentAccounts = data.paymentAccounts;
|
||||
this.loadVoucher(data.voucher);
|
||||
});
|
||||
this.hotkeys.add(
|
||||
new Hotkey(
|
||||
'f2',
|
||||
(event: KeyboardEvent): boolean => {
|
||||
setTimeout(() => {
|
||||
this.dateElement.nativeElement.focus();
|
||||
}, 0);
|
||||
return false; // Prevent bubbling
|
||||
},
|
||||
['INPUT', 'SELECT', 'TEXTAREA'],
|
||||
),
|
||||
);
|
||||
this.hotkeys.add(
|
||||
new Hotkey(
|
||||
'ctrl+s',
|
||||
(event: KeyboardEvent): boolean => {
|
||||
if (this.canSave()) this.save();
|
||||
return false; // Prevent bubbling
|
||||
},
|
||||
['INPUT', 'SELECT', 'TEXTAREA'],
|
||||
),
|
||||
);
|
||||
this.hotkeys.add(
|
||||
new Hotkey(
|
||||
'ctrl+p',
|
||||
(event: KeyboardEvent): boolean => {
|
||||
if (
|
||||
this.voucher.id &&
|
||||
!this.voucher.posted &&
|
||||
this.auth.user.perms.indexOf('post-vouchers') !== -1
|
||||
)
|
||||
this.post();
|
||||
return false; // Prevent bubbling
|
||||
},
|
||||
['INPUT', 'SELECT', 'TEXTAREA'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
@@ -91,20 +111,19 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
loadVoucher(voucher: Voucher) {
|
||||
this.voucher = voucher;
|
||||
this.paymentJournal = this.voucher.journals.filter(x => x.debit === -1)[0];
|
||||
this.paymentJournal = this.voucher.journals.filter((x) => x.debit === -1)[0];
|
||||
this.form.setValue({
|
||||
date: moment(this.voucher.date, 'DD-MMM-YYYY').toDate(),
|
||||
paymentAccount: this.paymentJournal.account.id,
|
||||
paymentAmount: this.paymentJournal.amount,
|
||||
addRow: {
|
||||
account: '',
|
||||
amount: 0
|
||||
amount: 0,
|
||||
},
|
||||
narration: this.voucher.narration
|
||||
narration: this.voucher.narration,
|
||||
});
|
||||
this.dataSource = new PaymentDataSource(this.journalObservable);
|
||||
this.updateView();
|
||||
|
||||
}
|
||||
|
||||
focusAccount() {
|
||||
@@ -132,7 +151,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
debit: debit,
|
||||
amount: amount,
|
||||
account: this.account,
|
||||
costCentre: null
|
||||
costCentre: null,
|
||||
});
|
||||
}
|
||||
this.resetAddRow();
|
||||
@@ -141,7 +160,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
rowAmount(amount: string = ''): number {
|
||||
try {
|
||||
amount = amount.replace(new RegExp('(₹[\s]*)|(,)|(\s)', 'g'), '');
|
||||
amount = amount.replace(new RegExp('(₹[s]*)|(,)|(s)', 'g'), '');
|
||||
return evaluate(amount);
|
||||
} catch {
|
||||
return 0;
|
||||
@@ -151,7 +170,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
resetAddRow() {
|
||||
this.form.get('addRow').reset({
|
||||
account: null,
|
||||
amount: null
|
||||
amount: null,
|
||||
});
|
||||
this.account = null;
|
||||
this.accBal = null;
|
||||
@@ -161,7 +180,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
updateView() {
|
||||
const journals = this.voucher.journals.filter(x => x.debit === 1);
|
||||
const journals = this.voucher.journals.filter((x) => x.debit === 1);
|
||||
this.journalObservable.next(journals);
|
||||
this.paymentJournal.amount = Math.abs(journals.map((x) => x.amount).reduce((p, c) => p + c, 0));
|
||||
this.form.get('paymentAmount').setValue(this.paymentJournal.amount);
|
||||
@@ -170,7 +189,10 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
editRow(row: Journal) {
|
||||
const dialogRef = this.dialog.open(PaymentDialogComponent, {
|
||||
width: '750px',
|
||||
data: {journal: Object.assign({}, row), date: moment(this.form.get('date').value).format('DD-MMM-YYYY')}
|
||||
data: {
|
||||
journal: Object.assign({}, row),
|
||||
date: moment(this.form.get('date').value).format('DD-MMM-YYYY'),
|
||||
},
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result: boolean | Journal) => {
|
||||
@@ -178,7 +200,10 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
const j = result as Journal;
|
||||
if (j.account.id !== row.account.id && this.voucher.journals.filter((x) => x.account.id === j.account.id).length) {
|
||||
if (
|
||||
j.account.id !== row.account.id &&
|
||||
this.voucher.journals.filter((x) => x.account.id === j.account.id).length
|
||||
) {
|
||||
return;
|
||||
}
|
||||
Object.assign(row, j);
|
||||
@@ -195,12 +220,12 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.form = this.fb.group({
|
||||
date: '',
|
||||
paymentAccount: '',
|
||||
paymentAmount: {value: '', disabled: true},
|
||||
paymentAmount: { value: '', disabled: true },
|
||||
addRow: this.fb.group({
|
||||
account: '',
|
||||
amount: ''
|
||||
amount: '',
|
||||
}),
|
||||
narration: ''
|
||||
narration: '',
|
||||
});
|
||||
this.accBal = null;
|
||||
}
|
||||
@@ -211,39 +236,40 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
} else if (this.voucher.posted && this.auth.user.perms.indexOf('edit-posted-vouchers') !== -1) {
|
||||
return true;
|
||||
} else {
|
||||
return this.voucher.user.id === this.auth.user.id || this.auth.user.perms.indexOf("edit-other-user's-vouchers") !== -1;
|
||||
return (
|
||||
this.voucher.user.id === this.auth.user.id ||
|
||||
this.auth.user.perms.indexOf("edit-other-user's-vouchers") !== -1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
post() {
|
||||
this.ser.post(this.voucher.id)
|
||||
.subscribe(
|
||||
(result) => {
|
||||
this.loadVoucher(result);
|
||||
this.toaster.show('Success', 'Voucher Posted');
|
||||
},
|
||||
(error) => {
|
||||
this.toaster.show('Danger', error);
|
||||
}
|
||||
);
|
||||
this.ser.post(this.voucher.id).subscribe(
|
||||
(result) => {
|
||||
this.loadVoucher(result);
|
||||
this.toaster.show('Success', 'Voucher Posted');
|
||||
},
|
||||
(error) => {
|
||||
this.toaster.show('Danger', error);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
save() {
|
||||
const voucher: Voucher = this.getVoucher();
|
||||
this.ser.saveOrUpdate(voucher)
|
||||
.subscribe(
|
||||
(result) => {
|
||||
this.toaster.show('Success', '');
|
||||
if (voucher.id === result.id) {
|
||||
this.loadVoucher(result);
|
||||
} else {
|
||||
this.ser.saveOrUpdate(voucher).subscribe(
|
||||
(result) => {
|
||||
this.toaster.show('Success', '');
|
||||
if (voucher.id === result.id) {
|
||||
this.loadVoucher(result);
|
||||
} else {
|
||||
this.router.navigate(['/payment', result.id]);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
this.toaster.show('Danger', error);
|
||||
}
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this.toaster.show('Danger', error);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
getVoucher(): Voucher {
|
||||
@@ -255,22 +281,21 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.ser.delete(this.voucher.id)
|
||||
.subscribe(
|
||||
(result) => {
|
||||
this.toaster.show('Success', '');
|
||||
this.router.navigate(['/payment'], {replaceUrl: true});
|
||||
},
|
||||
(error) => {
|
||||
this.toaster.show('Danger', error);
|
||||
}
|
||||
);
|
||||
this.ser.delete(this.voucher.id).subscribe(
|
||||
(result) => {
|
||||
this.toaster.show('Success', '');
|
||||
this.router.navigate(['/payment'], { replaceUrl: true });
|
||||
},
|
||||
(error) => {
|
||||
this.toaster.show('Danger', error);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
confirmDelete(): void {
|
||||
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
||||
width: '250px',
|
||||
data: {title: 'Delete Voucher?', content: 'Are you sure? This cannot be undone.'}
|
||||
data: { title: 'Delete Voucher?', content: 'Are you sure? This cannot be undone.' },
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result: boolean) => {
|
||||
@@ -282,24 +307,23 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
listenToAccountAutocompleteChange(): void {
|
||||
const control = this.form.get('addRow').get('account');
|
||||
this.accounts = control.valueChanges
|
||||
.pipe(
|
||||
startWith(null),
|
||||
map(x => (x !== null && x.length >= 1) ? x : null),
|
||||
debounceTime(150),
|
||||
distinctUntilChanged(),
|
||||
switchMap(x => (x === null) ? observableOf([]) : this.accountSer.autocomplete(x))
|
||||
);
|
||||
this.accounts = control.valueChanges.pipe(
|
||||
startWith(null),
|
||||
map((x) => (x !== null && x.length >= 1 ? x : null)),
|
||||
debounceTime(150),
|
||||
distinctUntilChanged(),
|
||||
switchMap((x) => (x === null ? observableOf([]) : this.accountSer.autocomplete(x))),
|
||||
);
|
||||
}
|
||||
|
||||
listenToPaymentAccountChange(): void {
|
||||
this.form.get('paymentAccount').valueChanges
|
||||
.subscribe(x => this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: {a: x},
|
||||
replaceUrl: true
|
||||
})
|
||||
);
|
||||
this.form.get('paymentAccount').valueChanges.subscribe((x) =>
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: { a: x },
|
||||
replaceUrl: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
displayAccount(account?: Account): string | undefined {
|
||||
@@ -317,7 +341,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
zoomImage(file: DbFile) {
|
||||
this.dialog.open(ImageDialogComponent, {
|
||||
width: '750px',
|
||||
data: file.resized
|
||||
data: file.resized,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -332,11 +356,12 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
for (const file of files) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e: any) => {
|
||||
zip(of(e.target.result),
|
||||
zip(
|
||||
of(e.target.result),
|
||||
this.resizeImage(e.target.result, 100, 150),
|
||||
this.resizeImage(e.target.result, 825, 1170)
|
||||
).subscribe(
|
||||
val => this.voucher.files.push({id: null, thumbnail: val[1], resized: val[2]})
|
||||
this.resizeImage(e.target.result, 825, 1170),
|
||||
).subscribe((val) =>
|
||||
this.voucher.files.push({ id: null, thumbnail: val[1], resized: val[2] }),
|
||||
);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
@@ -365,7 +390,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
canvas.height = height;
|
||||
ctx.drawImage(img, 0, 0, width, height);
|
||||
return canvas.toDataURL('image/jpeg', 0.95);
|
||||
})
|
||||
}),
|
||||
);
|
||||
img.src = image;
|
||||
return ex;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {PaymentModule} from './payment.module';
|
||||
import { PaymentModule } from './payment.module';
|
||||
|
||||
describe('PaymentModule', () => {
|
||||
let paymentModule: PaymentModule;
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE, MatNativeDateModule } from '@angular/material/core';
|
||||
import {
|
||||
DateAdapter,
|
||||
MAT_DATE_FORMATS,
|
||||
MAT_DATE_LOCALE,
|
||||
MatNativeDateModule,
|
||||
} from '@angular/material/core';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
@@ -15,16 +20,16 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import {SharedModule} from '../shared/shared.module';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
import {CdkTableModule} from '@angular/cdk/table';
|
||||
import {PaymentRoutingModule} from './payment-routing.module';
|
||||
import {PaymentComponent} from './payment.component';
|
||||
import {MomentDateAdapter} from '@angular/material-moment-adapter';
|
||||
import {A11yModule} from '@angular/cdk/a11y';
|
||||
import {PaymentDialogComponent} from './payment-dialog.component';
|
||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||
import {HotkeyModule} from "angular2-hotkeys";
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { CdkTableModule } from '@angular/cdk/table';
|
||||
import { PaymentRoutingModule } from './payment-routing.module';
|
||||
import { PaymentComponent } from './payment.component';
|
||||
import { MomentDateAdapter } from '@angular/material-moment-adapter';
|
||||
import { A11yModule } from '@angular/cdk/a11y';
|
||||
import { PaymentDialogComponent } from './payment-dialog.component';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { HotkeyModule } from 'angular2-hotkeys';
|
||||
|
||||
export const MY_FORMATS = {
|
||||
parse: {
|
||||
@@ -62,16 +67,12 @@ export const MY_FORMATS = {
|
||||
MatTableModule,
|
||||
ReactiveFormsModule,
|
||||
SharedModule,
|
||||
PaymentRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
PaymentComponent,
|
||||
PaymentDialogComponent
|
||||
PaymentRoutingModule,
|
||||
],
|
||||
declarations: [PaymentComponent, PaymentDialogComponent],
|
||||
providers: [
|
||||
{provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE]},
|
||||
{provide: MAT_DATE_FORMATS, useValue: MY_FORMATS},
|
||||
]
|
||||
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
|
||||
{ provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
|
||||
],
|
||||
})
|
||||
export class PaymentModule {
|
||||
}
|
||||
export class PaymentModule {}
|
||||
@@ -1,11 +1,11 @@
|
||||
import {inject, TestBed} from '@angular/core/testing';
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
|
||||
import {VoucherService} from './payment.service';
|
||||
import { VoucherService } from './payment.service';
|
||||
|
||||
describe('PaymentService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [VoucherService]
|
||||
providers: [VoucherService],
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user