diff --git a/overlord/package.json b/overlord/package.json index 69c05b36..5d85a746 100644 --- a/overlord/package.json +++ b/overlord/package.json @@ -50,6 +50,7 @@ "@angular/language-service": "^10.2.1", "@types/jasmine": "^3.6.0", "@types/jasminewd2": "^2.0.3", + "@types/mathjs": "^6.0.7", "@types/node": "^14.14.6", "@typescript-eslint/eslint-plugin": "^4.6.1", "@typescript-eslint/parser": "^4.6.1", diff --git a/overlord/src/app/account/account-detail/account-detail.component.ts b/overlord/src/app/account/account-detail/account-detail.component.ts index a040ade9..d5d88813 100644 --- a/overlord/src/app/account/account-detail/account-detail.component.ts +++ b/overlord/src/app/account/account-detail/account-detail.component.ts @@ -18,9 +18,9 @@ import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dial export class AccountDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; form: FormGroup; - accountTypes: AccountType[]; - costCentres: CostCentre[]; - item: Account; + accountTypes: AccountType[] = []; + costCentres: CostCentre[] = []; + item: Account = new Account(); constructor( private route: ActivatedRoute, @@ -68,7 +68,7 @@ export class AccountDetailComponent implements OnInit, AfterViewInit { ngAfterViewInit() { setTimeout(() => { - this.nameElement.nativeElement.focus(); + if (this.nameElement) this.nameElement.nativeElement.focus(); }, 0); } @@ -85,7 +85,7 @@ export class AccountDetailComponent implements OnInit, AfterViewInit { } delete() { - this.ser.delete(this.item.id).subscribe( + this.ser.delete(this.item.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigateByUrl('/accounts'); diff --git a/overlord/src/app/account/account-list/account-list-datasource.ts b/overlord/src/app/account/account-list/account-list-datasource.ts index 079709a1..4d4f97b6 100644 --- a/overlord/src/app/account/account-list/account-list-datasource.ts +++ b/overlord/src/app/account/account-list/account-list-datasource.ts @@ -12,7 +12,7 @@ function compare(a: string | number | boolean, b: string | number | boolean, isA return (a < b ? -1 : 1) * (isAsc ? 1 : -1); } export class AccountListDataSource extends DataSource { - private filterValue: string = ""; + private filterValue = ''; constructor( public data: Account[], diff --git a/overlord/src/app/account/account-list/account-list.component.ts b/overlord/src/app/account/account-list/account-list.component.ts index 2d690ddd..8b882e6f 100644 --- a/overlord/src/app/account/account-list/account-list.component.ts +++ b/overlord/src/app/account/account-list/account-list.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { ActivatedRoute } from '@angular/router'; @@ -22,7 +22,7 @@ export class AccountListComponent implements OnInit, AfterViewInit { dataSource: AccountListDataSource; filter: Observable; form: FormGroup; - list: Account[]; + list: Account[] = []; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['name', 'type', 'isActive', 'isReconcilable', 'costCentre']; @@ -31,13 +31,13 @@ export class AccountListComponent implements OnInit, AfterViewInit { this.form = this.fb.group({ filter: '', }); - this.filter = this.listenToFilterChange(); - } - - listenToFilterChange() { - return this.form - .get('filter') - .valueChanges.pipe(startWith(''), debounceTime(150), distinctUntilChanged()); + // Listen to Filter Change + this.filter = (this.form.get('filter') as FormControl).valueChanges.pipe( + startWith(''), + debounceTime(150), + distinctUntilChanged(), + ); + this.dataSource = new AccountListDataSource(this.list, this.filter); } ngOnInit() { @@ -46,12 +46,12 @@ export class AccountListComponent implements OnInit, AfterViewInit { this.list = data.list; }); - this.dataSource = new AccountListDataSource(this.paginator, this.sort, this.filter, this.list); + this.dataSource = new AccountListDataSource(this.list, this.filter, this.paginator, this.sort); } ngAfterViewInit() { setTimeout(() => { - this.filterElement.nativeElement.focus(); + if (this.filterElement) this.filterElement.nativeElement.focus(); }, 0); } } diff --git a/overlord/src/app/attendance/attendance-item.ts b/overlord/src/app/attendance/attendance-item.ts index ba174a51..68ab222f 100644 --- a/overlord/src/app/attendance/attendance-item.ts +++ b/overlord/src/app/attendance/attendance-item.ts @@ -12,6 +12,14 @@ export class AttendanceItem { fullDay?: boolean; public constructor(init?: Partial) { + this.id = ''; + this.code = 0; + this.name = ''; + this.designation = ''; + this.department = ''; + this.attendanceType = new AttendanceType(); + this.prints = ''; + this.hoursWorked = ''; Object.assign(this, init); } } diff --git a/overlord/src/app/attendance/attendance-type.ts b/overlord/src/app/attendance/attendance-type.ts index 6c2407ba..0283d248 100644 --- a/overlord/src/app/attendance/attendance-type.ts +++ b/overlord/src/app/attendance/attendance-type.ts @@ -4,6 +4,9 @@ export class AttendanceType { value: number; public constructor(init?: Partial) { + this.id = 0; + this.name = ''; + this.value = 1; Object.assign(this, init); } } diff --git a/overlord/src/app/attendance/attendance.component.ts b/overlord/src/app/attendance/attendance.component.ts index 6d1e49ce..16ef0e48 100644 --- a/overlord/src/app/attendance/attendance.component.ts +++ b/overlord/src/app/attendance/attendance.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { FormArray, FormBuilder, FormGroup } from '@angular/forms'; +import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; @@ -21,11 +21,11 @@ import { AttendanceService } from './attendance.service'; }) export class AttendanceComponent implements OnInit { public attendanceObservable = new BehaviorSubject([]); - dataSource: AttendanceDataSource; + dataSource: AttendanceDataSource = new AttendanceDataSource(this.attendanceObservable); form: FormGroup; - info: Attendance; - attendanceTypes: AttendanceType[]; + info: Attendance = new Attendance(); + attendanceTypes: AttendanceType[] = []; displayedColumns = ['code', 'name', 'designation', 'department', 'status', 'prints']; @@ -50,7 +50,9 @@ export class AttendanceComponent implements OnInit { this.info = data.info; this.attendanceTypes = data.attendanceTypes; - this.form.get('date').setValue(moment(this.info.date, 'DD-MMM-YYYY').toDate()); + (this.form.get('date') as FormControl).setValue( + moment(this.info.date, 'DD-MMM-YYYY').toDate(), + ); this.form.setControl( 'attendances', this.fb.array( diff --git a/overlord/src/app/attendance/attendance.service.ts b/overlord/src/app/attendance/attendance.service.ts index a49586e2..b93881b8 100644 --- a/overlord/src/app/attendance/attendance.service.ts +++ b/overlord/src/app/attendance/attendance.service.ts @@ -18,7 +18,7 @@ const serviceName = 'AttendanceService'; export class AttendanceService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - get(date: string): Observable { + get(date: string | null): Observable { const getUrl: string = date === null ? url : `${url}/${date}`; return >( this.http diff --git a/overlord/src/app/attendance/attendance.ts b/overlord/src/app/attendance/attendance.ts index f659146b..e7b1506c 100644 --- a/overlord/src/app/attendance/attendance.ts +++ b/overlord/src/app/attendance/attendance.ts @@ -5,6 +5,8 @@ export class Attendance { body: AttendanceItem[]; public constructor(init?: Partial) { + this.date = ''; + this.body = []; Object.assign(this, init); } } diff --git a/overlord/src/app/auth/auth.service.ts b/overlord/src/app/auth/auth.service.ts index dbf95ac8..3ac0bff6 100644 --- a/overlord/src/app/auth/auth.service.ts +++ b/overlord/src/app/auth/auth.service.ts @@ -12,15 +12,15 @@ const JWT_USER = 'JWT_USER'; @Injectable({ providedIn: 'root' }) export class AuthService { - private currentUserSubject: BehaviorSubject; - public currentUser: Observable; + private currentUserSubject: BehaviorSubject = new BehaviorSubject(null); + public currentUser: Observable; constructor(private http: HttpClient) { this.checkStorage(); this.currentUser = this.currentUserSubject.asObservable(); } - static parseJwt(token): User { + static parseJwt(token: string): User { const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const jsonPayload = decodeURIComponent( @@ -42,18 +42,22 @@ export class AuthService { }); } - checkStorage(): User { - const existingToken: User = JSON.parse(localStorage.getItem(JWT_USER)); + checkStorage(): User | null { + const storageToken = localStorage.getItem(JWT_USER); + let existingToken: User | null = null; + if (storageToken !== null) { + existingToken = JSON.parse(storageToken); + } if (existingToken === null || Date.now() > existingToken.exp * 1000) { localStorage.removeItem(JWT_USER); - this.currentUserSubject = new BehaviorSubject(null); + this.currentUserSubject.next(null); return null; } - this.currentUserSubject = new BehaviorSubject(existingToken); + this.currentUserSubject.next(existingToken); return existingToken; } - public get user(): User { + public get user(): User | null { let val = this.currentUserSubject.value; if (val == null) { return val; @@ -63,7 +67,7 @@ export class AuthService { val = this.checkStorage(); } if (val == null) { - return null; + return new User(); } expired = Date.now() > val.exp * 1000; if (expired) { @@ -94,7 +98,11 @@ export class AuthService { } needsRefreshing(): boolean { - return Date.now() > (this.user.exp - environment.ACCESS_TOKEN_REFRESH_MINUTES * 60) * 1000; + const { user } = this; + if (user === null) { + return true; + } + return Date.now() > (user.exp - environment.ACCESS_TOKEN_REFRESH_MINUTES * 60) * 1000; } logout() { @@ -118,4 +126,12 @@ export class AuthService { }), ); } + + allowed(permission: string): boolean { + const { user } = this; + if (user == null || user.perms.indexOf(permission) === -1) { + return false; + } + return true; + } } diff --git a/overlord/src/app/auth/login/login.component.ts b/overlord/src/app/auth/login/login.component.ts index ecbb10b5..e943b8da 100644 --- a/overlord/src/app/auth/login/login.component.ts +++ b/overlord/src/app/auth/login/login.component.ts @@ -16,8 +16,8 @@ export class LoginComponent implements OnInit, AfterViewInit { form: FormGroup; hide: boolean; showOtp: boolean; - clientId: string; - private returnUrl: string; + clientId = ''; + private returnUrl = ''; constructor( private route: ActivatedRoute, @@ -42,7 +42,7 @@ export class LoginComponent implements OnInit, AfterViewInit { ngAfterViewInit() { setTimeout(() => { - this.nameElement.nativeElement.focus(); + if (this.nameElement) this.nameElement.nativeElement.focus(); }, 0); } diff --git a/overlord/src/app/balance-sheet/balance-sheet-item.ts b/overlord/src/app/balance-sheet/balance-sheet-item.ts index 7b426880..d9762ccb 100644 --- a/overlord/src/app/balance-sheet/balance-sheet-item.ts +++ b/overlord/src/app/balance-sheet/balance-sheet-item.ts @@ -4,4 +4,9 @@ export class BalanceSheetItem { amount?: number; subAmount?: number; order: number; + + public constructor(init?: Partial) { + this.order = 0; + Object.assign(this, init); + } } diff --git a/overlord/src/app/balance-sheet/balance-sheet.component.ts b/overlord/src/app/balance-sheet/balance-sheet.component.ts index c471375c..04d0282e 100644 --- a/overlord/src/app/balance-sheet/balance-sheet.component.ts +++ b/overlord/src/app/balance-sheet/balance-sheet.component.ts @@ -16,9 +16,9 @@ import { BalanceSheetDataSource } from './balance-sheet-datasource'; export class BalanceSheetComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: BalanceSheetDataSource; + info: BalanceSheet = new BalanceSheet(); + dataSource: BalanceSheetDataSource = new BalanceSheetDataSource(this.info.body); form: FormGroup; - info: BalanceSheet; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['group', 'name', 'subAmount', 'total']; @@ -37,7 +37,7 @@ export class BalanceSheetComponent implements OnInit { date: moment(this.info.date, 'DD-MMM-YYYY').toDate(), }); }); - this.dataSource = new BalanceSheetDataSource(this.paginator, this.sort, this.info.body); + this.dataSource = new BalanceSheetDataSource(this.info.body, this.paginator, this.sort); } show() { diff --git a/overlord/src/app/balance-sheet/balance-sheet.service.ts b/overlord/src/app/balance-sheet/balance-sheet.service.ts index 34b75ec7..c9e834eb 100644 --- a/overlord/src/app/balance-sheet/balance-sheet.service.ts +++ b/overlord/src/app/balance-sheet/balance-sheet.service.ts @@ -16,7 +16,7 @@ const serviceName = 'BalanceSheetService'; export class BalanceSheetService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - list(date: string): Observable { + list(date: string | null): Observable { const listUrl = date === null ? url : `${url}/${date}`; return >( this.http diff --git a/overlord/src/app/balance-sheet/balance-sheet.ts b/overlord/src/app/balance-sheet/balance-sheet.ts index 5b2effab..3cc86f01 100644 --- a/overlord/src/app/balance-sheet/balance-sheet.ts +++ b/overlord/src/app/balance-sheet/balance-sheet.ts @@ -4,4 +4,10 @@ export class BalanceSheet { date: string; body: BalanceSheetItem[]; footer?: BalanceSheetItem; + + public constructor(init?: Partial) { + this.date = ''; + this.body = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/cash-flow/cash-flow-item.ts b/overlord/src/app/cash-flow/cash-flow-item.ts index bbb342a4..50bc6b0d 100644 --- a/overlord/src/app/cash-flow/cash-flow-item.ts +++ b/overlord/src/app/cash-flow/cash-flow-item.ts @@ -3,7 +3,10 @@ export class CashFlowItem { url: []; amount: number; - constructor(name: string) { - this.name = name; + public constructor(init?: Partial) { + this.name = ''; + this.url = []; + this.amount = 0; + Object.assign(this, init); } } diff --git a/overlord/src/app/cash-flow/cash-flow.component.ts b/overlord/src/app/cash-flow/cash-flow.component.ts index 5da8f0cc..4b98c41d 100644 --- a/overlord/src/app/cash-flow/cash-flow.component.ts +++ b/overlord/src/app/cash-flow/cash-flow.component.ts @@ -12,9 +12,9 @@ import { CashFlowDataSource } from './cash-flow-datasource'; styleUrls: ['./cash-flow.component.css'], }) export class CashFlowComponent implements OnInit { - dataSource: CashFlowDataSource; + info: CashFlow = new CashFlow(); + dataSource: CashFlowDataSource = new CashFlowDataSource(CashFlow.Data(this.info)); form: FormGroup; - info: CashFlow; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['name', 'amount']; @@ -51,9 +51,9 @@ export class CashFlowComponent implements OnInit { getInfo(): CashFlow { const formModel = this.form.value; - return { + return new CashFlow({ startDate: moment(formModel.startDate).format('DD-MMM-YYYY'), finishDate: moment(formModel.finishDate).format('DD-MMM-YYYY'), - }; + }); } } diff --git a/overlord/src/app/cash-flow/cash-flow.service.ts b/overlord/src/app/cash-flow/cash-flow.service.ts index 9c0bab08..900a2d3f 100644 --- a/overlord/src/app/cash-flow/cash-flow.service.ts +++ b/overlord/src/app/cash-flow/cash-flow.service.ts @@ -16,7 +16,11 @@ const serviceName = 'CashFlowService'; export class CashFlowService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - list(id: string, startDate: string, finishDate: string): Observable { + list( + id: string | null, + startDate: string | null, + finishDate: string | null, + ): Observable { const options = { params: new HttpParams() }; if (startDate !== null) { options.params = options.params.set('s', startDate); diff --git a/overlord/src/app/cash-flow/cash-flow.ts b/overlord/src/app/cash-flow/cash-flow.ts index 8fee5489..9b90749b 100644 --- a/overlord/src/app/cash-flow/cash-flow.ts +++ b/overlord/src/app/cash-flow/cash-flow.ts @@ -3,7 +3,7 @@ import { CashFlowItem } from './cash-flow-item'; export class CashFlow { startDate: string; finishDate: string; - body?: { + body: { operating: CashFlowItem[]; investing: CashFlowItem[]; financing: CashFlowItem[]; @@ -12,18 +12,18 @@ export class CashFlow { footer?: CashFlowItem[]; - static Data(value): CashFlowItem[] { + static Data(value: CashFlow): CashFlowItem[] { const d: CashFlowItem[] = []; if (value.body.operating?.length) { - d.push(new CashFlowItem('Cash flows from Operating activities')); + d.push(new CashFlowItem({ name: 'Cash flows from Operating activities' })); d.push(...value.body.operating); } if (value.body.investing?.length) { - d.push(new CashFlowItem('Cash flows from Investing activities')); + d.push(new CashFlowItem({ name: 'Cash flows from Investing activities' })); d.push(...value.body.investing); } if (value.body.financing?.length) { - d.push(new CashFlowItem('Cash flows from Financing activities')); + d.push(new CashFlowItem({ name: 'Cash flows from Financing activities' })); d.push(...value.body.financing); } if (value.body.details?.length) { @@ -31,4 +31,16 @@ export class CashFlow { } return d; } + + public constructor(init?: Partial) { + this.startDate = ''; + this.finishDate = ''; + this.body = { + operating: [], + investing: [], + financing: [], + details: [], + }; + Object.assign(this, init); + } } diff --git a/overlord/src/app/client/client-detail/client-detail.component.ts b/overlord/src/app/client/client-detail/client-detail.component.ts index f0a45514..44a50b4e 100644 --- a/overlord/src/app/client/client-detail/client-detail.component.ts +++ b/overlord/src/app/client/client-detail/client-detail.component.ts @@ -16,7 +16,7 @@ import { ClientService } from '../client.service'; export class ClientDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; form: FormGroup; - item: Client; + item: Client = new Client(); constructor( private route: ActivatedRoute, @@ -54,7 +54,7 @@ export class ClientDetailComponent implements OnInit, AfterViewInit { ngAfterViewInit() { setTimeout(() => { - this.nameElement.nativeElement.focus(); + if (this.nameElement) this.nameElement.nativeElement.focus(); }, 0); } diff --git a/overlord/src/app/client/client-list/client-list.component.ts b/overlord/src/app/client/client-list/client-list.component.ts index 65ab4569..c2488173 100644 --- a/overlord/src/app/client/client-list/client-list.component.ts +++ b/overlord/src/app/client/client-list/client-list.component.ts @@ -15,8 +15,8 @@ import { ClientListDataSource } from './client-list-datasource'; export class ClientListComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: ClientListDataSource; - list: Client[]; + list: Client[] = []; + dataSource: ClientListDataSource = new ClientListDataSource(this.list); /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['code', 'name', 'enabled', 'otp', 'created', 'lastLogin']; @@ -28,6 +28,6 @@ export class ClientListComponent implements OnInit { this.list = data.list; }); - this.dataSource = new ClientListDataSource(this.paginator, this.sort, this.list); + this.dataSource = new ClientListDataSource(this.list, this.paginator, this.sort); } } diff --git a/overlord/src/app/client/client.service.ts b/overlord/src/app/client/client.service.ts index 0274b98b..0f11ffca 100644 --- a/overlord/src/app/client/client.service.ts +++ b/overlord/src/app/client/client.service.ts @@ -19,7 +19,7 @@ const serviceName = 'ClientService'; export class ClientService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - get(id: string): Observable { + get(id: string | null): Observable { const getUrl: string = id === null ? url : `${url}/${id}`; return >( this.http diff --git a/overlord/src/app/client/client.ts b/overlord/src/app/client/client.ts index 4b2c6352..9a821e36 100644 --- a/overlord/src/app/client/client.ts +++ b/overlord/src/app/client/client.ts @@ -6,4 +6,15 @@ export class Client { otp: number; creationDate: string; lastLogin: string; + + public constructor(init?: Partial) { + this.id = ''; + this.name = ''; + this.code = 0; + this.enabled = true; + this.otp = 0; + this.creationDate = ''; + this.lastLogin = ''; + Object.assign(this, init); + } } diff --git a/overlord/src/app/closing-stock/closing-stock-item.ts b/overlord/src/app/closing-stock/closing-stock-item.ts index 251fe541..54c3bb5b 100644 --- a/overlord/src/app/closing-stock/closing-stock-item.ts +++ b/overlord/src/app/closing-stock/closing-stock-item.ts @@ -3,4 +3,12 @@ export class ClosingStockItem { group: string; quantity: number; amount: number; + + public constructor(init?: Partial) { + this.product = ''; + this.group = ''; + this.quantity = 0; + this.amount = 0; + Object.assign(this, init); + } } diff --git a/overlord/src/app/closing-stock/closing-stock.component.ts b/overlord/src/app/closing-stock/closing-stock.component.ts index 68813737..6a299d48 100644 --- a/overlord/src/app/closing-stock/closing-stock.component.ts +++ b/overlord/src/app/closing-stock/closing-stock.component.ts @@ -18,9 +18,9 @@ import { ClosingStockDataSource } from './closing-stock-datasource'; export class ClosingStockComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: ClosingStockDataSource; + info: ClosingStock = new ClosingStock(); + dataSource: ClosingStockDataSource = new ClosingStockDataSource(this.info.body); form: FormGroup; - info: ClosingStock; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['product', 'group', 'quantity', 'amount']; @@ -44,7 +44,7 @@ export class ClosingStockComponent implements OnInit { date: moment(this.info.date, 'DD-MMM-YYYY').toDate(), }); }); - this.dataSource = new ClosingStockDataSource(this.paginator, this.sort, this.info.body); + this.dataSource = new ClosingStockDataSource(this.info.body, this.paginator, this.sort); } show() { @@ -55,9 +55,9 @@ export class ClosingStockComponent implements OnInit { getInfo(): ClosingStock { const formModel = this.form.value; - return { + return new ClosingStock({ date: moment(formModel.date).format('DD-MMM-YYYY'), - }; + }); } exportCsv() { diff --git a/overlord/src/app/closing-stock/closing-stock.service.ts b/overlord/src/app/closing-stock/closing-stock.service.ts index 934e37b9..1f136227 100644 --- a/overlord/src/app/closing-stock/closing-stock.service.ts +++ b/overlord/src/app/closing-stock/closing-stock.service.ts @@ -16,7 +16,7 @@ const serviceName = 'ClosingStockService'; export class ClosingStockService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - list(date: string): Observable { + list(date: string | null): Observable { const listUrl = date === null ? url : `${url}/${date}`; return >( this.http diff --git a/overlord/src/app/closing-stock/closing-stock.ts b/overlord/src/app/closing-stock/closing-stock.ts index 88f51401..682643e4 100644 --- a/overlord/src/app/closing-stock/closing-stock.ts +++ b/overlord/src/app/closing-stock/closing-stock.ts @@ -2,5 +2,11 @@ import { ClosingStockItem } from './closing-stock-item'; export class ClosingStock { date: string; - body?: ClosingStockItem[]; + body: ClosingStockItem[]; + + public constructor(init?: Partial) { + this.date = ''; + this.body = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/core/account-type.ts b/overlord/src/app/core/account-type.ts index efc55bce..4582df84 100644 --- a/overlord/src/app/core/account-type.ts +++ b/overlord/src/app/core/account-type.ts @@ -9,10 +9,10 @@ export class AccountType { public constructor(init?: Partial) { this.id = 0; - this.name = ""; + this.name = ''; this.balanceSheet = false; this.debit = false; - this.cashFlowClassification = ""; + this.cashFlowClassification = ''; this.order = 0; this.showInList = true; Object.assign(this, init); diff --git a/overlord/src/app/core/account.service.ts b/overlord/src/app/core/account.service.ts index c45b3a82..e8753a3a 100644 --- a/overlord/src/app/core/account.service.ts +++ b/overlord/src/app/core/account.service.ts @@ -17,7 +17,7 @@ const serviceName = 'AccountService'; export class AccountService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - get(id: string): Observable { + get(id: string | null): Observable { const getUrl: string = id === null ? `${url}` : `${url}/${id}`; return >( this.http diff --git a/overlord/src/app/core/account.ts b/overlord/src/app/core/account.ts index 83796741..d513a0c9 100644 --- a/overlord/src/app/core/account.ts +++ b/overlord/src/app/core/account.ts @@ -14,12 +14,12 @@ export class Account { public constructor(init?: Partial) { this.code = 0; - this.name = ""; + this.name = ''; this.type = new AccountType(); - this.isActive = true; + this.isActive = true; this.isReconcilable = false; this.isStarred = false; - this.isFixture= false; + this.isFixture = false; this.costCentre = new CostCentre(); Object.assign(this, init); } diff --git a/overlord/src/app/core/batch.ts b/overlord/src/app/core/batch.ts index 03cfe7b4..90b485c2 100644 --- a/overlord/src/app/core/batch.ts +++ b/overlord/src/app/core/batch.ts @@ -8,4 +8,15 @@ export class Batch { discount: number; rate: number; product: Product; + + public constructor(init?: Partial) { + this.id = ''; + this.name = ''; + this.quantityRemaining = 0; + this.tax = 0; + this.discount = 0; + this.rate = 0; + this.product = new Product(); + Object.assign(this, init); + } } diff --git a/overlord/src/app/core/cost-centre.ts b/overlord/src/app/core/cost-centre.ts index 84d1883a..261b9930 100644 --- a/overlord/src/app/core/cost-centre.ts +++ b/overlord/src/app/core/cost-centre.ts @@ -4,7 +4,7 @@ export class CostCentre { isFixture: boolean; public constructor(init?: Partial) { - this.name = ""; + this.name = ''; this.isFixture = false; Object.assign(this, init); } diff --git a/overlord/src/app/core/db-file.ts b/overlord/src/app/core/db-file.ts index 2643c4c5..5c4189f9 100644 --- a/overlord/src/app/core/db-file.ts +++ b/overlord/src/app/core/db-file.ts @@ -1,5 +1,11 @@ export class DbFile { - id: string; + id: string | undefined; resized: string; thumbnail: string; + + public constructor(init?: Partial) { + this.resized = ''; + this.thumbnail = ''; + Object.assign(this, init); + } } diff --git a/overlord/src/app/core/employee-benefit.ts b/overlord/src/app/core/employee-benefit.ts index 3ba7ca7f..d4671bf1 100644 --- a/overlord/src/app/core/employee-benefit.ts +++ b/overlord/src/app/core/employee-benefit.ts @@ -8,4 +8,15 @@ export class EmployeeBenefit { esiEmployer: number; pfEmployer: number; employee: Employee; + + public constructor(init?: Partial) { + this.grossSalary = 0; + this.daysWorked = 0; + this.esiEmployee = 0; + this.pfEmployee = 0; + this.esiEmployer = 0; + this.pfEmployer = 0; + this.employee = new Employee(); + Object.assign(this, init); + } } diff --git a/overlord/src/app/core/incentive.ts b/overlord/src/app/core/incentive.ts index ecc76793..13289c00 100644 --- a/overlord/src/app/core/incentive.ts +++ b/overlord/src/app/core/incentive.ts @@ -1,8 +1,18 @@ export class Incentive { - id: string; + id: string | undefined; name: string; designation: string; department: string; daysWorked: number; points: number; + + public constructor(init?: Partial) { + this.name = ''; + this.designation = ''; + this.designation = ''; + this.department = ''; + this.daysWorked = 0; + this.points = 0; + Object.assign(this, init); + } } diff --git a/overlord/src/app/core/inventory.ts b/overlord/src/app/core/inventory.ts index b3c7727a..79289d99 100644 --- a/overlord/src/app/core/inventory.ts +++ b/overlord/src/app/core/inventory.ts @@ -2,12 +2,23 @@ import { Batch } from './batch'; import { Product } from './product'; export class Inventory { - id: string; + id: string | undefined; quantity: number; rate: number; tax: number; discount: number; amount: number; product: Product; - batch: Batch; + batch: Batch | null; + + public constructor(init?: Partial) { + this.quantity = 0; + this.rate = 0; + this.tax = 0; + this.discount = 0; + this.amount = 0; + this.product = new Product(); + this.batch = new Batch(); + Object.assign(this, init); + } } diff --git a/overlord/src/app/core/journal.ts b/overlord/src/app/core/journal.ts index 99dae0d1..5fa29e87 100644 --- a/overlord/src/app/core/journal.ts +++ b/overlord/src/app/core/journal.ts @@ -2,13 +2,17 @@ import { Account } from './account'; import { CostCentre } from './cost-centre'; export class Journal { - id: string; + id: string | undefined; debit: number; amount: number; account: Account; - costCentre: CostCentre; + costCentre: CostCentre | null; public constructor(init?: Partial) { + this.debit = 0; + this.amount = 0; + this.account = new Account(); + this.costCentre = new CostCentre(); Object.assign(this, init); } } diff --git a/overlord/src/app/core/product-group.ts b/overlord/src/app/core/product-group.ts index 86645914..1067ddbb 100644 --- a/overlord/src/app/core/product-group.ts +++ b/overlord/src/app/core/product-group.ts @@ -1,5 +1,11 @@ export class ProductGroup { - id: string; + id: string | undefined; name: string; isFixture: boolean; + + public constructor(init?: Partial) { + this.name = ''; + this.isFixture = false; + Object.assign(this, init); + } } diff --git a/overlord/src/app/core/product.ts b/overlord/src/app/core/product.ts index 9eb9e637..208748aa 100644 --- a/overlord/src/app/core/product.ts +++ b/overlord/src/app/core/product.ts @@ -2,7 +2,7 @@ import { Account } from './account'; import { ProductGroup } from './product-group'; export class Product { - id: string; + id: string | undefined; code: number; name: string; units: string; @@ -17,4 +17,22 @@ export class Product { isSold: boolean; productGroup: ProductGroup; account: Account; + + public constructor(init?: Partial) { + this.code = 0; + this.name = ''; + this.units = ''; + this.fraction = 1; + this.fractionUnits = ''; + this.productYield = 1; + this.price = 0; + this.salePrice = 0; + this.isActive = true; + this.isFixture = false; + this.isPurchased = true; + this.isSold = false; + this.productGroup = new ProductGroup(); + this.account = new Account(); + Object.assign(this, init); + } } diff --git a/overlord/src/app/core/user-group.ts b/overlord/src/app/core/user-group.ts index ce7f90a5..517a00ea 100644 --- a/overlord/src/app/core/user-group.ts +++ b/overlord/src/app/core/user-group.ts @@ -1,5 +1,11 @@ export class UserGroup { - id: string; + id: string | undefined; name: string; enabled: boolean; + + public constructor(init?: Partial) { + this.name = ''; + this.enabled = true; + Object.assign(this, init); + } } diff --git a/overlord/src/app/core/user.ts b/overlord/src/app/core/user.ts index de6d7f15..17fdbc3c 100644 --- a/overlord/src/app/core/user.ts +++ b/overlord/src/app/core/user.ts @@ -9,7 +9,7 @@ export class User { perms: string[]; isAuthenticated: boolean; access_token?: string; - exp?: number; + exp: number; ver: string; public constructor(init?: Partial) { @@ -19,6 +19,7 @@ export class User { this.roles = []; this.perms = []; this.isAuthenticated = false; + this.exp = 0; this.ver = ''; Object.assign(this, init); } diff --git a/overlord/src/app/core/voucher.service.ts b/overlord/src/app/core/voucher.service.ts index af7f65f3..d141f067 100644 --- a/overlord/src/app/core/voucher.service.ts +++ b/overlord/src/app/core/voucher.service.ts @@ -19,11 +19,11 @@ const serviceName = 'VoucherService'; export class VoucherService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - static dataURLtoBlob(dataURL) { + static dataURLtoBlob(dataURL: string) { const re = /^data:([\w/\-.]+);\w+,(.*)$/; const m = dataURL.match(re); - const mimeString = m[1]; - const byteString = atob(m[2]); + const mimeString = (m as RegExpMatchArray)[1]; + const byteString = atob((m as RegExpMatchArray)[2]); const ab = new ArrayBuffer(byteString.length); const dw = new DataView(ab); let i; @@ -45,10 +45,10 @@ export class VoucherService { getOfType(type: string, account?: string): Observable { const endpoint = type.replace(/ /g, '-').toLowerCase(); - const options = {}; + let options = {}; if (account !== undefined && account !== null) { // eslint-disable-next-line @typescript-eslint/dot-notation - options['params'] = new HttpParams().set('a', account); + options = { params: new HttpParams().set('a', account) }; } return >( this.http diff --git a/overlord/src/app/core/voucher.ts b/overlord/src/app/core/voucher.ts index 88b82e0a..d876704c 100644 --- a/overlord/src/app/core/voucher.ts +++ b/overlord/src/app/core/voucher.ts @@ -8,7 +8,7 @@ import { Journal } from './journal'; import { User } from './user'; export class Voucher { - id: string; + id: string | undefined; date: string; type: string; posted: boolean; @@ -28,4 +28,24 @@ export class Voucher { isStarred: boolean; poster: string; reconcileDate: string; + + public constructor(init?: Partial) { + this.id = ''; + this.date = ''; + this.type = ''; + this.posted = false; + this.narration = ''; + this.journals = []; + this.inventories = []; + this.employeeBenefits = []; + this.incentives = []; + this.files = []; + this.creationDate = ''; + this.lastEditDate = ''; + this.user = new User(); + this.isStarred = false; + this.poster = ''; + this.reconcileDate = ''; + Object.assign(this, init); + } } diff --git a/overlord/src/app/cost-centre/cost-centre-detail/cost-centre-detail.component.ts b/overlord/src/app/cost-centre/cost-centre-detail/cost-centre-detail.component.ts index 2781e3ae..22f192a8 100644 --- a/overlord/src/app/cost-centre/cost-centre-detail/cost-centre-detail.component.ts +++ b/overlord/src/app/cost-centre/cost-centre-detail/cost-centre-detail.component.ts @@ -14,7 +14,7 @@ import { CostCentreService } from '../cost-centre.service'; export class CostCentreDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; form: FormGroup; - item: CostCentre; + item: CostCentre = new CostCentre(); constructor( private route: ActivatedRoute, @@ -45,7 +45,7 @@ export class CostCentreDetailComponent implements OnInit, AfterViewInit { ngAfterViewInit() { setTimeout(() => { - this.nameElement.nativeElement.focus(); + if (this.nameElement) this.nameElement.nativeElement.focus(); }, 0); } diff --git a/overlord/src/app/cost-centre/cost-centre-list/cost-centre-list.component.ts b/overlord/src/app/cost-centre/cost-centre-list/cost-centre-list.component.ts index 0c11a5f3..c7b4e2e4 100644 --- a/overlord/src/app/cost-centre/cost-centre-list/cost-centre-list.component.ts +++ b/overlord/src/app/cost-centre/cost-centre-list/cost-centre-list.component.ts @@ -15,8 +15,8 @@ import { CostCentreListDataSource } from './cost-centre-list-datasource'; export class CostCentreListComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: CostCentreListDataSource; - list: CostCentre[]; + list: CostCentre[] = []; + dataSource: CostCentreListDataSource = new CostCentreListDataSource(this.list); /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['name', 'isFixture']; @@ -28,6 +28,6 @@ export class CostCentreListComponent implements OnInit { this.list = data.list; }); - this.dataSource = new CostCentreListDataSource(this.paginator, this.sort, this.list); + this.dataSource = new CostCentreListDataSource(this.list, this.paginator, this.sort); } } diff --git a/overlord/src/app/cost-centre/cost-centre.service.ts b/overlord/src/app/cost-centre/cost-centre.service.ts index 4abf056a..250b8faf 100644 --- a/overlord/src/app/cost-centre/cost-centre.service.ts +++ b/overlord/src/app/cost-centre/cost-centre.service.ts @@ -18,7 +18,7 @@ const serviceName = 'CostCentreService'; export class CostCentreService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - get(id: string): Observable { + get(id: string | null): Observable { const getUrl: string = id === null ? `${url}` : `${url}/${id}`; return >( this.http diff --git a/overlord/src/app/daybook/daybook-item.ts b/overlord/src/app/daybook/daybook-item.ts index 58f45821..17a6914d 100644 --- a/overlord/src/app/daybook/daybook-item.ts +++ b/overlord/src/app/daybook/daybook-item.ts @@ -9,4 +9,18 @@ export class DaybookItem { creditAmount: number; posted: boolean; url: string[]; + + public constructor(init?: Partial) { + this.id = ''; + this.date = ''; + this.type = ''; + this.narration = ''; + this.debitText = ''; + this.debitAmount = 0; + this.creditText = ''; + this.creditAmount = 0; + this.posted = true; + this.url = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/daybook/daybook.component.ts b/overlord/src/app/daybook/daybook.component.ts index 9edd9466..b0f6ef6c 100644 --- a/overlord/src/app/daybook/daybook.component.ts +++ b/overlord/src/app/daybook/daybook.component.ts @@ -17,10 +17,10 @@ import { DaybookService } from './daybook.service'; export class DaybookComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: DaybookDataSource; + info: Daybook = new Daybook(); + dataSource: DaybookDataSource = new DaybookDataSource(this.info.body); form: FormGroup; - info: Daybook; - selectedRowId: string; + selectedRowId = ''; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = [ 'date', @@ -53,7 +53,7 @@ export class DaybookComponent implements OnInit { startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(), finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(), }); - this.dataSource = new DaybookDataSource(this.paginator, this.sort, this.info.body); + this.dataSource = new DaybookDataSource(this.info.body, this.paginator, this.sort); }); } diff --git a/overlord/src/app/daybook/daybook.service.ts b/overlord/src/app/daybook/daybook.service.ts index f961163f..9a36cca8 100644 --- a/overlord/src/app/daybook/daybook.service.ts +++ b/overlord/src/app/daybook/daybook.service.ts @@ -16,7 +16,7 @@ const serviceName = 'DaybookService'; export class DaybookService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - list(startDate: string, finishDate): Observable { + list(startDate: string | null, finishDate: string | null): Observable { const startDateWithSlash = startDate ? `/${startDate}` : ''; const finishDateWithSlash = finishDate ? `/${finishDate}` : ''; return >( diff --git a/overlord/src/app/daybook/daybook.ts b/overlord/src/app/daybook/daybook.ts index f77f1752..c2e22998 100644 --- a/overlord/src/app/daybook/daybook.ts +++ b/overlord/src/app/daybook/daybook.ts @@ -4,4 +4,11 @@ export class Daybook { startDate: string; finishDate: string; body: DaybookItem[]; + + public constructor(init?: Partial) { + this.startDate = ''; + this.finishDate = ''; + this.body = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/employee-attendance/employee-attendance-item.ts b/overlord/src/app/employee-attendance/employee-attendance-item.ts index 99b4c235..c2eb3e9b 100644 --- a/overlord/src/app/employee-attendance/employee-attendance-item.ts +++ b/overlord/src/app/employee-attendance/employee-attendance-item.ts @@ -5,9 +5,14 @@ export class EmployeeAttendanceItem { attendanceType: AttendanceType; prints: string; hoursWorked: string; - fullDay?: boolean; + fullDay: boolean; public constructor(init?: Partial) { + this.date = ''; + this.attendanceType = new AttendanceType(); + this.prints = ''; + this.hoursWorked = ''; + this.fullDay = true; Object.assign(this, init); } } diff --git a/overlord/src/app/employee-attendance/employee-attendance.component.ts b/overlord/src/app/employee-attendance/employee-attendance.component.ts index 50504e76..292a9022 100644 --- a/overlord/src/app/employee-attendance/employee-attendance.component.ts +++ b/overlord/src/app/employee-attendance/employee-attendance.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { FormArray, FormBuilder, FormGroup } from '@angular/forms'; +import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -26,11 +26,14 @@ import { EmployeeAttendanceService } from './employee-attendance.service'; export class EmployeeAttendanceComponent implements OnInit, AfterViewInit { @ViewChild('employeeElement', { static: true }) employeeElement?: ElementRef; public employeeAttendanceObservable = new BehaviorSubject([]); - dataSource: EmployeeAttendanceDataSource; + dataSource: EmployeeAttendanceDataSource = new EmployeeAttendanceDataSource( + this.employeeAttendanceObservable, + ); + form: FormGroup; - info: EmployeeAttendance; - attendanceTypes: AttendanceType[]; + info: EmployeeAttendance = new EmployeeAttendance(); + attendanceTypes: AttendanceType[] = []; displayedColumns = ['date', 'status', 'prints']; @@ -52,7 +55,14 @@ export class EmployeeAttendanceComponent implements OnInit, AfterViewInit { employee: '', attendances: this.fb.array([]), }); - this.listenToEmployeeValueChanges(); + // Listen to Employee Value Changes + this.employees = (this.form.get('employee') as FormControl).valueChanges.pipe( + startWith(null), + map((x) => (x !== null && x.length >= 1 ? x : null)), + debounceTime(150), + distinctUntilChanged(), + switchMap((x) => (x === null ? observableOf([]) : this.employeeSer.autocomplete(x))), + ); } ngOnInit() { @@ -61,9 +71,13 @@ export class EmployeeAttendanceComponent implements OnInit, AfterViewInit { this.info = data.info; this.attendanceTypes = data.attendanceTypes; - this.form.get('startDate').setValue(moment(this.info.startDate, 'DD-MMM-YYYY').toDate()); - this.form.get('finishDate').setValue(moment(this.info.finishDate, 'DD-MMM-YYYY').toDate()); - this.form.get('employee').setValue(this.info.employee); + (this.form.get('startDate') as FormControl).setValue( + moment(this.info.startDate, 'DD-MMM-YYYY').toDate(), + ); + (this.form.get('finishDate') as FormControl).setValue( + moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(), + ); + (this.form.get('employee') as FormControl).setValue(this.info.employee); this.form.setControl( 'attendances', this.fb.array( @@ -81,22 +95,12 @@ export class EmployeeAttendanceComponent implements OnInit, AfterViewInit { ngAfterViewInit() { setTimeout(() => { - this.employeeElement.nativeElement.focus(); + if (this.employeeElement) this.employeeElement.nativeElement.focus(); }, 0); } - listenToEmployeeValueChanges() { - this.employees = this.form.get('employee').valueChanges.pipe( - startWith(null), - map((x) => (x !== null && x.length >= 1 ? x : null)), - debounceTime(150), - distinctUntilChanged(), - switchMap((x) => (x === null ? observableOf([]) : this.employeeSer.autocomplete(x))), - ); - } - - displayFn(employee?: Employee): string | undefined { - return employee ? employee.name : undefined; + displayFn(employee?: Employee): string { + return employee ? employee.name : ''; } selected(event: MatAutocompleteSelectedEvent): void { diff --git a/overlord/src/app/employee-attendance/employee-attendance.service.ts b/overlord/src/app/employee-attendance/employee-attendance.service.ts index 73aa4acd..ec06dd22 100644 --- a/overlord/src/app/employee-attendance/employee-attendance.service.ts +++ b/overlord/src/app/employee-attendance/employee-attendance.service.ts @@ -18,7 +18,11 @@ const serviceName = 'EmployeeAttendanceService'; export class EmployeeAttendanceService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - get(id: string, startDate: string, finishDate: string): Observable { + get( + id: string | null, + startDate: string | null, + finishDate: string | null, + ): Observable { const getUrl: string = id === null ? url : `${url}/${id}`; const options = { params: new HttpParams() }; if (startDate !== null) { diff --git a/overlord/src/app/employee-attendance/employee-attendance.ts b/overlord/src/app/employee-attendance/employee-attendance.ts index ac1ad9fd..a874975b 100644 --- a/overlord/src/app/employee-attendance/employee-attendance.ts +++ b/overlord/src/app/employee-attendance/employee-attendance.ts @@ -9,6 +9,10 @@ export class EmployeeAttendance { body: EmployeeAttendanceItem[]; public constructor(init?: Partial) { + this.startDate = ''; + this.finishDate = ''; + this.employee = new Employee(); + this.body = []; Object.assign(this, init); } } diff --git a/overlord/src/app/employee-benefits/employee-benefits.component.html b/overlord/src/app/employee-benefits/employee-benefits.component.html index 60eb9ed3..b67e2c88 100644 --- a/overlord/src/app/employee-benefits/employee-benefits.component.html +++ b/overlord/src/app/employee-benefits/employee-benefits.component.html @@ -159,7 +159,7 @@ mat-raised-button (click)="post()" *ngIf="voucher.id" - [disabled]="voucher.posted || auth.user.perms.indexOf('post-vouchers') === -1" + [disabled]="voucher.posted || auth.allowed('post-vouchers')" > {{ voucher.posted ? 'Posted' : 'Post' }} diff --git a/overlord/src/app/employee-benefits/employee-benefits.component.ts b/overlord/src/app/employee-benefits/employee-benefits.component.ts index ded13462..0d154685 100644 --- a/overlord/src/app/employee-benefits/employee-benefits.component.ts +++ b/overlord/src/app/employee-benefits/employee-benefits.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -10,6 +10,7 @@ import { debounceTime, distinctUntilChanged, map, startWith, switchMap } from 'r import { AuthService } from '../auth/auth.service'; import { EmployeeBenefit } from '../core/employee-benefit'; import { ToasterService } from '../core/toaster.service'; +import { User } from '../core/user'; import { Voucher } from '../core/voucher'; import { VoucherService } from '../core/voucher.service'; import { Employee } from '../employee/employee'; @@ -26,10 +27,10 @@ import { EmployeeBenefitsDataSource } from './employee-benefits-datasource'; export class EmployeeBenefitsComponent implements OnInit, AfterViewInit { @ViewChild('employeeElement', { static: true }) employeeElement?: ElementRef; public benefitsObservable = new BehaviorSubject([]); - dataSource: EmployeeBenefitsDataSource; + dataSource: EmployeeBenefitsDataSource = new EmployeeBenefitsDataSource(this.benefitsObservable); form: FormGroup; - voucher: Voucher; - employee: Employee; + voucher: Voucher = new Voucher(); + employee: Employee | null = null; accBal: any; displayedColumns = [ @@ -64,10 +65,19 @@ export class EmployeeBenefitsComponent implements OnInit, AfterViewInit { daysWorked: '', }), }); - this.setupEmployeeAutocomplete(); + // Setup Employee Autocomplete + this.employees = ((this.form.get('addRow') as FormControl).get( + 'employee', + ) as FormControl).valueChanges.pipe( + startWith(null), + map((x) => (x !== null && x.length >= 1 ? x : null)), + debounceTime(150), + distinctUntilChanged(), + switchMap((x) => (x === null ? observableOf([]) : this.employeeSer.autocomplete(x))), + ); } - static getPf(grossSalary, daysWorked, daysInMonth) { + static getPf(grossSalary: number, daysWorked: number, daysInMonth: number) { const limit = 15000; const employeeRate = 0.12; const employerRate = 0.12 + 0.011 + 0.005 + 0.0001; @@ -78,7 +88,7 @@ export class EmployeeBenefitsComponent implements OnInit, AfterViewInit { return { ee: employee, er: employer, both: employee + employer }; } - static getEsi(grossSalary, daysWorked, daysInMonth) { + static getEsi(grossSalary: number, daysWorked: number, daysInMonth: number) { const limit = 21000; const employeeRate = 0.0175; const employerRate = 0.0475; @@ -99,11 +109,11 @@ export class EmployeeBenefitsComponent implements OnInit, AfterViewInit { ngAfterViewInit() { setTimeout(() => { - this.employeeElement.nativeElement.focus(); + if (this.employeeElement) this.employeeElement.nativeElement.focus(); }, 0); } - loadVoucher(voucher) { + loadVoucher(voucher: Voucher) { this.voucher = voucher; this.form.setValue({ date: moment(this.voucher.date, 'DD-MMM-YYYY').toDate(), @@ -117,19 +127,8 @@ export class EmployeeBenefitsComponent implements OnInit, AfterViewInit { this.benefitsObservable.next(this.voucher.employeeBenefits); } - setupEmployeeAutocomplete(): void { - const control = this.form.get('addRow').get('employee'); - this.employees = control.valueChanges.pipe( - startWith(null), - map((x) => (x !== null && x.length >= 1 ? x : null)), - debounceTime(150), - distinctUntilChanged(), - switchMap((x) => (x === null ? observableOf([]) : this.employeeSer.autocomplete(x))), - ); - } - - displayFn(employee?: Employee): string | undefined { - return employee ? employee.name : undefined; + displayFn(employee?: Employee): string { + return employee ? employee.name : ''; } employeeSelected(event: MatAutocompleteSelectedEvent): void { @@ -138,43 +137,45 @@ export class EmployeeBenefitsComponent implements OnInit, AfterViewInit { addRow() { const oldFiltered = this.voucher.employeeBenefits.filter( - (x) => x.employee.id === this.employee.id, + (x) => x.employee.id === (this.employee as Employee).id, ); if (oldFiltered.length) { this.toaster.show('Danger', 'Employee has already been added'); return; } - const formValue = this.form.get('addRow').value; + const formValue = (this.form.get('addRow') as FormControl).value; const grossSalary = +formValue.grossSalary; const daysWorked = +formValue.daysWorked; - const date = this.form.get('date').value; + const date = (this.form.get('date') as FormControl).value; const daysInMonth = moment(date).daysInMonth(); const esi = EmployeeBenefitsComponent.getEsi(grossSalary, daysWorked, daysInMonth); const pf = EmployeeBenefitsComponent.getPf(grossSalary, daysWorked, daysInMonth); - this.voucher.employeeBenefits.push({ - employee: this.employee, - grossSalary, - daysWorked, - esiEmployee: esi.ee, - pfEmployee: pf.ee, - esiEmployer: esi.er, - pfEmployer: pf.er, - }); + this.voucher.employeeBenefits.push( + new EmployeeBenefit({ + employee: this.employee as Employee, + grossSalary, + daysWorked, + esiEmployee: esi.ee, + pfEmployee: pf.ee, + esiEmployer: esi.er, + pfEmployer: pf.er, + }), + ); this.benefitsObservable.next(this.voucher.employeeBenefits); this.resetAddRow(); } resetAddRow() { - this.form.get('addRow').reset({ + (this.form.get('addRow') as FormControl).reset({ employee: '', grossSalary: null, daysWorked: null, }); this.employee = null; setTimeout(() => { - this.employeeElement.nativeElement.focus(); + if (this.employeeElement) this.employeeElement.nativeElement.focus(); }, 0); } @@ -187,17 +188,17 @@ export class EmployeeBenefitsComponent implements OnInit, AfterViewInit { if (!this.voucher.id) { return true; } - if (this.voucher.posted && this.auth.user.perms.indexOf('edit-posted-vouchers') !== -1) { + if (this.voucher.posted && this.auth.allowed('edit-posted-vouchers')) { return true; } return ( - this.voucher.user.id === this.auth.user.id || - this.auth.user.perms.indexOf("edit-other-user's-vouchers") !== -1 + this.voucher.user.id === (this.auth.user as User).id || + this.auth.allowed("edit-other-user's-vouchers") ); } post() { - this.ser.post(this.voucher.id).subscribe( + this.ser.post(this.voucher.id as string).subscribe( (result) => { this.loadVoucher(result); this.toaster.show('Success', 'Voucher Posted'); @@ -232,7 +233,7 @@ export class EmployeeBenefitsComponent implements OnInit, AfterViewInit { } delete() { - this.ser.delete(this.voucher.id).subscribe( + this.ser.delete(this.voucher.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigate(['/employee-benefits'], { replaceUrl: true }); diff --git a/overlord/src/app/employee-functions/employee-functions.component.ts b/overlord/src/app/employee-functions/employee-functions.component.ts index 78e5e15e..26df5539 100644 --- a/overlord/src/app/employee-functions/employee-functions.component.ts +++ b/overlord/src/app/employee-functions/employee-functions.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatDatepicker } from '@angular/material/datepicker'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -20,7 +20,7 @@ export class EmployeeFunctionsComponent { creditSalaryForm: FormGroup; attendanceRecordForm: FormGroup; fingerprintForm: FormGroup; - fingerprintFile: File; + fingerprintFile: File | null = null; constructor( private route: ActivatedRoute, @@ -44,7 +44,7 @@ export class EmployeeFunctionsComponent { } chosenYearHandler(normalizedYear: Moment) { - const dateControl = this.creditSalaryForm.get('date'); + const dateControl = this.creditSalaryForm.get('date') as FormControl; const ctrlValue = dateControl.value; ctrlValue.year(normalizedYear.year()); ctrlValue.date(ctrlValue.daysInMonth()); @@ -52,7 +52,7 @@ export class EmployeeFunctionsComponent { } chosenMonthHandler(normlizedMonth: Moment, datepicker: MatDatepicker) { - const dateControl = this.creditSalaryForm.get('date'); + const dateControl = this.creditSalaryForm.get('date') as FormControl; const ctrlValue = dateControl.value; ctrlValue.month(normlizedMonth.month()); ctrlValue.date(ctrlValue.daysInMonth()); @@ -61,7 +61,9 @@ export class EmployeeFunctionsComponent { } creditSalary() { - const date = moment(this.creditSalaryForm.get('date').value).format('DD-MMM-YYYY'); + const date = moment((this.creditSalaryForm.get('date') as FormControl).value).format( + 'DD-MMM-YYYY', + ); if (!date) { this.toaster.show('Danger', 'Please choose a valid date.'); return; @@ -77,12 +79,12 @@ export class EmployeeFunctionsComponent { } attendanceRecordUrl() { - const startDate = moment(this.attendanceRecordForm.get('startDate').value).format( - 'DD-MMM-YYYY', - ); - const finishDate = moment(this.attendanceRecordForm.get('finishDate').value).format( - 'DD-MMM-YYYY', - ); + const startDate = moment( + (this.attendanceRecordForm.get('startDate') as FormControl).value, + ).format('DD-MMM-YYYY'); + const finishDate = moment( + (this.attendanceRecordForm.get('finishDate') as FormControl).value, + ).format('DD-MMM-YYYY'); if (!startDate || !finishDate) { // this.toaster.show('Danger', 'Please choose a start and finish date.'); return ''; @@ -90,8 +92,9 @@ export class EmployeeFunctionsComponent { return `/attendance-report?s=${startDate}&f=${finishDate}`; } - detectFiles(event) { - [this.fingerprintFile] = event.target.files; + detectFiles(event: Event) { + // eslint-disable-next-line prefer-destructuring + this.fingerprintFile = ((event.target as HTMLInputElement).files as FileList)[0]; } uploadFingerprints() { diff --git a/overlord/src/app/employee/employee-detail/employee-detail.component.ts b/overlord/src/app/employee/employee-detail/employee-detail.component.ts index d662e667..2c65d106 100644 --- a/overlord/src/app/employee/employee-detail/employee-detail.component.ts +++ b/overlord/src/app/employee/employee-detail/employee-detail.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; @@ -18,8 +18,8 @@ import { EmployeeService } from '../employee.service'; export class EmployeeDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; form: FormGroup; - costCentres: CostCentre[]; - item: Employee; + costCentres: CostCentre[] = []; + item: Employee = new Employee(); constructor( private route: ActivatedRoute, @@ -40,7 +40,10 @@ export class EmployeeDetailComponent implements OnInit, AfterViewInit { joiningDate: '', leavingDate: '', }); - this.listenToIsActiveChanges(); + // Listen to IsActive Changes + (this.form.get('isActive') as FormControl).valueChanges.subscribe((x) => { + this.item.isActive = x; + }); } ngOnInit() { @@ -73,16 +76,10 @@ export class EmployeeDetailComponent implements OnInit, AfterViewInit { ngAfterViewInit() { setTimeout(() => { - this.nameElement.nativeElement.focus(); + if (this.nameElement) this.nameElement.nativeElement.focus(); }, 0); } - listenToIsActiveChanges(): void { - this.form.get('isActive').valueChanges.subscribe((x) => { - this.item.isActive = x; - }); - } - save() { this.ser.saveOrUpdate(this.getItem()).subscribe( () => { @@ -96,7 +93,7 @@ export class EmployeeDetailComponent implements OnInit, AfterViewInit { } delete() { - this.ser.delete(this.item.id).subscribe( + this.ser.delete(this.item.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigateByUrl('/employees'); diff --git a/overlord/src/app/employee/employee-list/employee-list-datasource.ts b/overlord/src/app/employee/employee-list/employee-list-datasource.ts index 652c5c90..04d1c3fe 100644 --- a/overlord/src/app/employee/employee-list/employee-list-datasource.ts +++ b/overlord/src/app/employee/employee-list/employee-list-datasource.ts @@ -12,7 +12,7 @@ function compare(a: string | number, b: string | number, isAsc: boolean) { return (a < b ? -1 : 1) * (isAsc ? 1 : -1); } export class EmployeeListDataSource extends DataSource { - private filterValue: string = ""; + private filterValue = ''; constructor( public data: Employee[], @@ -94,7 +94,7 @@ export class EmployeeListDataSource extends DataSource { case 'joiningDate': return compare(a.joiningDate, b.joiningDate, isAsc); case 'leavingDate': - return compare(a.leavingDate || "", b.leavingDate || "", isAsc); + return compare(a.leavingDate || '', b.leavingDate || '', isAsc); default: return 0; } diff --git a/overlord/src/app/employee/employee-list/employee-list.component.ts b/overlord/src/app/employee/employee-list/employee-list.component.ts index c68d2ec2..676901ff 100644 --- a/overlord/src/app/employee/employee-list/employee-list.component.ts +++ b/overlord/src/app/employee/employee-list/employee-list.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { ActivatedRoute } from '@angular/router'; @@ -23,7 +23,7 @@ export class EmployeeListComponent implements OnInit, AfterViewInit { dataSource: EmployeeListDataSource; filter: Observable; form: FormGroup; - list: Employee[]; + list: Employee[] = []; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = [ @@ -41,13 +41,12 @@ export class EmployeeListComponent implements OnInit, AfterViewInit { this.form = this.fb.group({ filter: '', }); - this.filter = this.listenToFilterChange(); - } - - listenToFilterChange() { - return this.form - .get('filter') - .valueChanges.pipe(startWith(''), debounceTime(150), distinctUntilChanged()); + this.filter = (this.form.get('filter') as FormControl).valueChanges.pipe( + startWith(''), + debounceTime(150), + distinctUntilChanged(), + ); + this.dataSource = new EmployeeListDataSource(this.list, this.filter); } ngOnInit() { @@ -56,12 +55,12 @@ export class EmployeeListComponent implements OnInit, AfterViewInit { this.list = data.list; }); - this.dataSource = new EmployeeListDataSource(this.paginator, this.sort, this.filter, this.list); + this.dataSource = new EmployeeListDataSource(this.list, this.filter, this.paginator, this.sort); } ngAfterViewInit() { setTimeout(() => { - this.filterElement.nativeElement.focus(); + if (this.filterElement) this.filterElement.nativeElement.focus(); }, 0); } diff --git a/overlord/src/app/employee/employee.service.ts b/overlord/src/app/employee/employee.service.ts index 4fc99a33..cc22a232 100644 --- a/overlord/src/app/employee/employee.service.ts +++ b/overlord/src/app/employee/employee.service.ts @@ -18,7 +18,7 @@ const serviceName = 'EmployeeService'; export class EmployeeService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - get(id: string): Observable { + get(id: string | null): Observable { const getUrl: string = id === null ? `${url}` : `${url}/${id}`; return >( this.http diff --git a/overlord/src/app/employee/employee.ts b/overlord/src/app/employee/employee.ts index b3497655..f4207ef4 100644 --- a/overlord/src/app/employee/employee.ts +++ b/overlord/src/app/employee/employee.ts @@ -10,19 +10,19 @@ export class Employee { salary: number; points: number; joiningDate: string; - leavingDate?: string; + leavingDate: string | null; costCentre: CostCentre; - public constructor(init?: Partial) { this.code = 0; - this.name = ""; + this.name = ''; this.isStarred = false; this.isActive = true; - this.designation = ""; + this.designation = ''; this.salary = 0; this.points = 0; - this.joiningDate = ""; + this.joiningDate = ''; + this.leavingDate = null; this.costCentre = new CostCentre(); Object.assign(this, init); } diff --git a/overlord/src/app/incentive/incentive.component.html b/overlord/src/app/incentive/incentive.component.html index b5ad11db..c18566af 100644 --- a/overlord/src/app/incentive/incentive.component.html +++ b/overlord/src/app/incentive/incentive.component.html @@ -92,7 +92,7 @@ mat-raised-button (click)="post()" *ngIf="voucher.id" - [disabled]="voucher.posted || auth.user.perms.indexOf('post-vouchers') === -1" + [disabled]="voucher.posted || auth.allowed('post-vouchers')" > {{ voucher.posted ? 'Posted' : 'Post' }} diff --git a/overlord/src/app/incentive/incentive.component.ts b/overlord/src/app/incentive/incentive.component.ts index d4909572..170e0040 100644 --- a/overlord/src/app/incentive/incentive.component.ts +++ b/overlord/src/app/incentive/incentive.component.ts @@ -1,15 +1,16 @@ import { Component, OnInit } from '@angular/core'; -import { FormArray, FormBuilder, FormGroup } from '@angular/forms'; +import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { map } from 'rxjs/operators'; import { AuthService } from '../auth/auth.service'; import { Account } from '../core/account'; import { Incentive } from '../core/incentive'; import { ToasterService } from '../core/toaster.service'; +import { User } from '../core/user'; import { Voucher } from '../core/voucher'; import { VoucherService } from '../core/voucher.service'; import { ConfirmDialogComponent } from '../shared/confirm-dialog/confirm-dialog.component'; @@ -23,16 +24,14 @@ import { IncentiveDataSource } from './incentive-datasource'; }) export class IncentiveComponent implements OnInit { public incentiveObservable = new BehaviorSubject([]); - dataSource: IncentiveDataSource; + dataSource: IncentiveDataSource = new IncentiveDataSource(this.incentiveObservable); form: FormGroup; - voucher: Voucher; - account: Account; + voucher: Voucher = new Voucher(); + account: Account = new Account(); accBal: any; displayedColumns = ['name', 'designation', 'department', 'daysWorked', 'points', 'amount']; - accounts: Observable; - constructor( private route: ActivatedRoute, private router: Router, @@ -46,7 +45,17 @@ export class IncentiveComponent implements OnInit { date: '', incentives: this.fb.array([]), }); - this.listenToDateChange(); + // Listen to Date Change + (this.form.get('date') as FormControl).valueChanges + .pipe(map((x) => moment(x).format('DD-MMM-YYYY'))) + .subscribe((x) => { + if (x !== this.voucher.date && !this.voucher.id) { + return this.ser.getIncentive(x).subscribe((voucher: Voucher) => { + this.loadVoucher(voucher); + }); + } + return ''; + }); } ngOnInit() { @@ -57,9 +66,11 @@ export class IncentiveComponent implements OnInit { }); } - loadVoucher(voucher) { + loadVoucher(voucher: Voucher) { this.voucher = voucher; - this.form.get('date').setValue(moment(this.voucher.date, 'DD-MMM-YYYY').toDate()); + (this.form.get('date') as FormControl).setValue( + moment(this.voucher.date, 'DD-MMM-YYYY').toDate(), + ); this.form.setControl( 'incentives', this.fb.array( @@ -74,20 +85,6 @@ export class IncentiveComponent implements OnInit { this.incentiveObservable.next(this.voucher.incentives); } - listenToDateChange(): void { - this.form - .get('date') - .valueChanges.pipe(map((x) => moment(x).format('DD-MMM-YYYY'))) - .subscribe((x) => { - if (x !== this.voucher.date && !this.voucher.id) { - return this.ser.getIncentive(x).subscribe((voucher: Voucher) => { - this.loadVoucher(voucher); - }); - } - return ''; - }); - } - totalPoints() { return this.voucher.incentives .map((item) => item.daysWorked * item.points) @@ -95,36 +92,35 @@ export class IncentiveComponent implements OnInit { } pointValue() { - return Math.round((this.voucher.incentive * 100) / this.totalPoints()) / 100; + return Math.round(((this.voucher.incentive as number) * 100) / this.totalPoints()) / 100; } less(row: Incentive, i: number) { if (row.points >= 1) { row.points -= 1; - this.form - .get('incentives') - .get(`${i}`) - .setValue({ points: `${row.points}` }); + ((this.form.get('incentives') as FormArray).get(`${i}`) as FormGroup).setValue({ + points: `${row.points}`, + }); } } change(row: Incentive, i: number) { - row.points = +this.form.get('incentives').get(`${i}`).get('points').value; - this.form - .get('incentives') - .get(`${i}`) - .setValue({ points: `${row.points}` }); + row.points = +(((this.form.get('incentives') as FormArray).get(`${i}`) as FormGroup).get( + 'points', + ) as FormControl).value; + ((this.form.get('incentives') as FormArray).get(`${i}`) as FormGroup).setValue({ + points: `${row.points}`, + }); } more(row: Incentive, i: number) { row.points += 1; - this.form - .get('incentives') - .get(`${i}`) - .setValue({ points: `${row.points}` }); + ((this.form.get('incentives') as FormArray).get(`${i}`) as FormGroup).setValue({ + points: `${row.points}`, + }); } - amount(row) { + amount(row: Incentive) { return row.points * row.daysWorked * this.pointValue(); } @@ -132,17 +128,17 @@ export class IncentiveComponent implements OnInit { if (!this.voucher.id) { return true; } - if (this.voucher.posted && this.auth.user.perms.indexOf('edit-posted-vouchers') !== -1) { + if (this.voucher.posted && this.auth.allowed('edit-posted-vouchers')) { return true; } return ( - this.voucher.user.id === this.auth.user.id || - this.auth.user.perms.indexOf("edit-other-user's-vouchers") !== -1 + this.voucher.user.id === (this.auth.user as User).id || + this.auth.allowed("edit-other-user's-vouchers") ); } post() { - this.ser.post(this.voucher.id).subscribe( + this.ser.post(this.voucher.id as string).subscribe( (result) => { this.loadVoucher(result); this.toaster.show('Success', 'Voucher Posted'); @@ -180,7 +176,7 @@ export class IncentiveComponent implements OnInit { } delete() { - this.ser.delete(this.voucher.id).subscribe( + this.ser.delete(this.voucher.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigate(['/incentive'], { replaceUrl: true }); diff --git a/overlord/src/app/issue/issue-dialog.component.ts b/overlord/src/app/issue/issue-dialog.component.ts index 67c84c7f..b9293d6e 100644 --- a/overlord/src/app/issue/issue-dialog.component.ts +++ b/overlord/src/app/issue/issue-dialog.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Observable } from 'rxjs'; @@ -17,7 +17,7 @@ import { MathService } from '../shared/math.service'; export class IssueDialogComponent implements OnInit { batches: Observable; form: FormGroup; - batch: Batch; + batch: Batch = new Batch(); constructor( public dialogRef: MatDialogRef, @@ -30,7 +30,13 @@ export class IssueDialogComponent implements OnInit { batch: '', quantity: '', }); - this.listenToBatchAutocompleteChange(); + // Listen to Batch Autocomplete Change + this.batches = (this.form.get('batch') as FormControl).valueChanges.pipe( + startWith(''), + debounceTime(150), + distinctUntilChanged(), + switchMap((x) => this.batchSer.autocomplete(this.data.date, x)), + ); } ngOnInit() { @@ -41,18 +47,8 @@ export class IssueDialogComponent implements OnInit { this.batch = this.data.inventory.batch; } - listenToBatchAutocompleteChange(): void { - const control = this.form.get('batch'); - this.batches = control.valueChanges.pipe( - startWith(''), - debounceTime(150), - distinctUntilChanged(), - switchMap((x) => this.batchSer.autocomplete(this.data.date, x)), - ); - } - - displayFn(batch?: Batch): string | undefined { - return batch ? batch.name : undefined; + displayFn(batch?: Batch): string { + return batch ? batch.name : ''; } batchSelected(event: MatAutocompleteSelectedEvent): void { diff --git a/overlord/src/app/issue/issue.component.ts b/overlord/src/app/issue/issue.component.ts index ef967b70..1f6ce6e8 100644 --- a/overlord/src/app/issue/issue.component.ts +++ b/overlord/src/app/issue/issue.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -15,6 +15,7 @@ import { BatchService } from '../core/batch.service'; import { CostCentre } from '../core/cost-centre'; import { Inventory } from '../core/inventory'; import { ToasterService } from '../core/toaster.service'; +import { User } from '../core/user'; import { Voucher } from '../core/voucher'; import { VoucherService } from '../core/voucher.service'; import { ConfirmDialogComponent } from '../shared/confirm-dialog/confirm-dialog.component'; @@ -35,12 +36,12 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild('dateElement', { static: true }) dateElement?: ElementRef; public inventoryObservable = new BehaviorSubject([]); public gridObservable = new BehaviorSubject([]); - dataSource: IssueDataSource; - gridDataSource: IssueGridDataSource; + dataSource: IssueDataSource = new IssueDataSource(this.inventoryObservable); + gridDataSource: IssueGridDataSource = new IssueGridDataSource(this.gridObservable); form: FormGroup; - voucher: Voucher; - costCentres: CostCentre[]; - batch: Batch; + voucher: Voucher = new Voucher(); + costCentres: CostCentre[] = []; + batch: Batch | null = null; displayedColumns = ['product', 'batch', 'quantity', 'rate', 'amount', 'action']; gridColumns = ['source', 'destination', 'gridAmount', 'load']; @@ -71,8 +72,21 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy { }), narration: '', }); - this.listenToBatchAutocompleteChange(); - this.listenToDateChange(); + // Listen to Batch Autocomplete Change + this.batches = ((this.form.get('addRow') as FormControl).get( + 'batch', + ) as FormControl).valueChanges.pipe( + startWith('null'), + debounceTime(150), + distinctUntilChanged(), + switchMap((x) => + this.batchSer.autocomplete(moment(this.form.value.date).format('DD-MMM-YYYY'), x), + ), + ); + // Listen to Date Change + (this.form.get('date') as FormControl).valueChanges + .pipe(map((x) => moment(x).format('DD-MMM-YYYY'))) + .subscribe((x) => this.showGrid(x)); } ngOnInit() { @@ -88,7 +102,7 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy { 'f2', (): boolean => { setTimeout(() => { - this.dateElement.nativeElement.focus(); + if (this.dateElement) this.dateElement.nativeElement.focus(); }, 0); return false; // Prevent bubbling }, @@ -121,8 +135,8 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy { this.voucher = voucher; this.form.setValue({ date: moment(this.voucher.date, 'DD-MMM-YYYY').toDate(), - source: this.voucher.source.id, - destination: this.voucher.destination.id, + source: (this.voucher.source as CostCentre).id, + destination: (this.voucher.destination as CostCentre).id, amount: Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0)), addRow: { batch: '', @@ -136,59 +150,60 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy { focusBatch() { setTimeout(() => { - this.batchElement.nativeElement.focus(); + if (this.batchElement) this.batchElement.nativeElement.focus(); }, 0); } addRow() { - const formValue = this.form.get('addRow').value; + const formValue = (this.form.get('addRow') as FormControl).value; const quantity = this.math.parseAmount(formValue.quantity, 2); const isConsumption = this.form.value.source === '7b845f95-dfef-fa4a-897c-f0baf15284a3'; if (this.batch === null || quantity <= 0) { return; } const oldFiltered = this.voucher.inventories.filter( - (x) => x.product.id === this.batch.product.id, + (x) => x.product.id === (this.batch as Batch).product.id, ); const old = oldFiltered.length ? oldFiltered[0] : null; if (oldFiltered.length) { - if (old.batch.id !== this.batch.id) { + if (((old as Inventory).batch as Batch).id !== this.batch.id) { this.toaster.show('Danger', 'Product with a different batch already added'); return; } - if (isConsumption && old.quantity + quantity > this.batch.quantityRemaining) { + if (isConsumption && (old as Inventory).quantity + quantity > this.batch.quantityRemaining) { this.toaster.show('Danger', 'Quantity issued cannot be more than quantity available'); return; } - old.quantity += quantity; + (old as Inventory).quantity += quantity; } else { if (isConsumption && quantity > this.batch.quantityRemaining) { this.toaster.show('Danger', 'Quantity issued cannot be more than quantity available'); return; } - this.voucher.inventories.push({ - id: null, - quantity, - rate: this.batch.rate, - tax: this.batch.tax, - discount: this.batch.discount, - amount: quantity * this.batch.rate * (1 + this.batch.tax) * (1 - this.batch.discount), - product: this.batch.product, - batch: this.batch, - }); + this.voucher.inventories.push( + new Inventory({ + quantity, + rate: this.batch.rate, + tax: this.batch.tax, + discount: this.batch.discount, + amount: quantity * this.batch.rate * (1 + this.batch.tax) * (1 - this.batch.discount), + product: this.batch.product, + batch: this.batch, + }), + ); } this.resetAddRow(); this.updateView(); } resetAddRow() { - this.form.get('addRow').reset({ + (this.form.get('addRow') as FormControl).reset({ batch: null, quantity: '', }); this.batch = null; setTimeout(() => { - this.batchElement.nativeElement.focus(); + if (this.batchElement) this.batchElement.nativeElement.focus(); }, 0); } @@ -198,7 +213,7 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy { Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0)), 2, ); - this.form.get('amount').setValue(amount); + (this.form.get('amount') as FormControl).setValue(amount); } editRow(row: Inventory) { @@ -235,12 +250,12 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy { if (!this.voucher.id) { return true; } - if (this.voucher.posted && this.auth.user.perms.indexOf('edit-posted-vouchers') !== -1) { + if (this.voucher.posted && this.auth.allowed('edit-posted-vouchers')) { return true; } return ( - this.voucher.user.id === this.auth.user.id || - this.auth.user.perms.indexOf("edit-other-user's-vouchers") !== -1 + this.voucher.user.id === (this.auth.user as User).id || + this.auth.allowed("edit-other-user's-vouchers") ); } @@ -268,14 +283,14 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy { getVoucher(): Voucher { const formModel = this.form.value; this.voucher.date = moment(formModel.date).format('DD-MMM-YYYY'); - this.voucher.source.id = formModel.source; - this.voucher.destination.id = formModel.destination; + (this.voucher.source as CostCentre).id = formModel.source; + (this.voucher.destination as CostCentre).id = formModel.destination; this.voucher.narration = formModel.narration; return this.voucher; } delete() { - this.ser.delete(this.voucher.id).subscribe( + this.ser.delete(this.voucher.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigate(['/issue'], { replaceUrl: true }); @@ -299,31 +314,12 @@ export class IssueComponent implements OnInit, AfterViewInit, OnDestroy { }); } - listenToBatchAutocompleteChange(): void { - const control = this.form.get('addRow').get('batch'); - this.batches = control.valueChanges.pipe( - startWith('null'), - debounceTime(150), - distinctUntilChanged(), - switchMap((x) => - this.batchSer.autocomplete(moment(this.form.value.date).format('DD-MMM-YYYY'), x), - ), - ); - } - - listenToDateChange(): void { - this.form - .get('date') - .valueChanges.pipe(map((x) => moment(x).format('DD-MMM-YYYY'))) - .subscribe((x) => this.showGrid(x)); - } - showGrid(date: string) { this.issueGridSer.issueGrid(date).subscribe((x) => this.gridObservable.next(x)); } - displayFn(batch?: Batch): string | undefined { - return batch ? batch.name : undefined; + displayFn(batch?: Batch): string { + return batch ? batch.name : ''; } batchSelected(event: MatAutocompleteSelectedEvent): void { diff --git a/overlord/src/app/journal/journal-dialog.component.ts b/overlord/src/app/journal/journal-dialog.component.ts index 65470cf2..a6b56bbf 100644 --- a/overlord/src/app/journal/journal-dialog.component.ts +++ b/overlord/src/app/journal/journal-dialog.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Observable, of as observableOf } from 'rxjs'; @@ -17,7 +17,7 @@ import { MathService } from '../shared/math.service'; export class JournalDialogComponent implements OnInit { accounts: Observable; form: FormGroup; - account: Account; + account: Account = new Account(); accBal: any; constructor( @@ -33,7 +33,14 @@ export class JournalDialogComponent implements OnInit { amount: '', }); this.accBal = null; - this.setupAccountAutocomplete(); + // Setup Account Autocomplete + this.accounts = (this.form.get('account') as FormControl).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))), + ); } ngOnInit() { @@ -45,24 +52,14 @@ export class JournalDialogComponent implements OnInit { this.account = this.data.journal.account; } - 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))), - ); - } - - displayFn(account?: Account): string | undefined { - return account ? account.name : undefined; + displayFn(account?: Account): string { + return account ? account.name : ''; } accountSelected(event: MatAutocompleteSelectedEvent): void { - this.account = event.option.value; - this.accountSer.balance(this.account.id, this.data.date).subscribe((v) => { + const account = event.option.value; + this.account = account; + this.accountSer.balance(account.id as string, this.data.date).subscribe((v) => { this.accBal = v; }); } diff --git a/overlord/src/app/journal/journal.component.html b/overlord/src/app/journal/journal.component.html index 49137bf7..11b61ee3 100644 --- a/overlord/src/app/journal/journal.component.html +++ b/overlord/src/app/journal/journal.component.html @@ -159,7 +159,7 @@ mat-raised-button (click)="post()" *ngIf="voucher.id" - [disabled]="voucher.posted || auth.user.perms.indexOf('post-vouchers') === -1" + [disabled]="voucher.posted || auth.allowed('post-vouchers')" > {{ voucher.posted ? 'Posted' : 'Post' }} diff --git a/overlord/src/app/journal/journal.component.ts b/overlord/src/app/journal/journal.component.ts index df7da68d..8cbc18df 100644 --- a/overlord/src/app/journal/journal.component.ts +++ b/overlord/src/app/journal/journal.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -15,6 +15,7 @@ import { AccountService } from '../core/account.service'; import { DbFile } from '../core/db-file'; import { Journal } from '../core/journal'; import { ToasterService } from '../core/toaster.service'; +import { User } from '../core/user'; import { Voucher } from '../core/voucher'; import { VoucherService } from '../core/voucher.service'; import { ConfirmDialogComponent } from '../shared/confirm-dialog/confirm-dialog.component'; @@ -34,10 +35,10 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild('accountElement', { static: true }) accountElement?: ElementRef; @ViewChild('dateElement', { static: true }) dateElement?: ElementRef; public journalObservable = new BehaviorSubject([]); - dataSource: JournalDataSource; + dataSource: JournalDataSource = new JournalDataSource(this.journalObservable); form: FormGroup; - voucher: Voucher; - account: Account; + voucher: Voucher = new Voucher(); + account: Account | null; accBal: any; displayedColumns = ['debit', 'account', 'amount', 'action']; @@ -68,7 +69,16 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { narration: '', }); this.accBal = null; - this.setupAccountAutocomplete(); + // Setup Account Autocomplete + this.accounts = ((this.form.get('addRow') as FormControl).get( + 'account', + ) as FormControl).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))), + ); } ngOnInit() { @@ -82,7 +92,7 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { 'f2', (): boolean => { setTimeout(() => { - this.dateElement.nativeElement.focus(); + if (this.dateElement) this.dateElement.nativeElement.focus(); }, 0); return false; // Prevent bubbling }, @@ -105,11 +115,7 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { new Hotkey( 'ctrl+p', (): boolean => { - if ( - this.voucher.id && - !this.voucher.posted && - this.auth.user.perms.indexOf('post-vouchers') !== -1 - ) { + if (this.voucher.id && !this.voucher.posted && this.auth.allowed('post-vouchers')) { this.post(); } return false; // Prevent bubbling @@ -127,7 +133,7 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { this.hotkeys.reset(); } - loadVoucher(voucher) { + loadVoucher(voucher: Voucher) { this.voucher = voucher; this.form.setValue({ date: moment(this.voucher.date, 'DD-MMM-YYYY').toDate(), @@ -144,30 +150,33 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { focusAccount() { setTimeout(() => { - this.accountElement.nativeElement.focus(); + if (this.accountElement) this.accountElement.nativeElement.focus(); }, 0); } addRow() { - const formValue = this.form.get('addRow').value; + const formValue = (this.form.get('addRow') as FormControl).value; const amount = this.math.journalAmount(formValue.amount, +formValue.debit); if (this.account === null || amount.amount === 0) { return; } - const oldFiltered = this.voucher.journals.filter((x) => x.account.id === this.account.id); + const oldFiltered = this.voucher.journals.filter( + (x) => x.account.id === (this.account as Account).id, + ); const old = oldFiltered.length ? oldFiltered[0] : null; if (old) { const a = old.debit * old.amount + amount.debit * amount.amount; old.debit = a < 0 ? -1 : 1; old.amount = Math.abs(a); } else { - this.voucher.journals.push({ - id: null, - debit: amount.debit, - amount: amount.amount, - account: this.account, - costCentre: null, - }); + this.voucher.journals.push( + new Journal({ + debit: amount.debit, + amount: amount.amount, + account: this.account, + costCentre: null, + }), + ); } this.journalObservable.next(this.voucher.journals); this.resetAddRow(); @@ -178,8 +187,8 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { Math.abs(this.voucher.journals.map((x) => x.debit * x.amount).reduce((p, c) => p + c)), 2, ); - const { debit } = this.form.get('addRow').value; - this.form.get('addRow').reset({ + const { debit } = (this.form.get('addRow') as FormControl).value; + (this.form.get('addRow') as FormControl).reset({ debit, account: null, amount: `${amount}`, @@ -187,7 +196,7 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { this.account = null; this.accBal = null; setTimeout(() => { - this.accountElement.nativeElement.focus(); + if (this.accountElement) this.accountElement.nativeElement.focus(); }, 0); } @@ -196,7 +205,7 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { width: '750px', data: { journal: { ...row }, - date: moment(this.form.get('date').value).format('DD-MMM-YYYY'), + date: moment((this.form.get('date') as FormControl).value).format('DD-MMM-YYYY'), }, }); @@ -223,7 +232,7 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { const amount = Math.abs( this.voucher.journals.map((x) => x.debit * x.amount).reduce((p, c) => p + c, 0), ); - this.form.get('addRow').patchValue({ + (this.form.get('addRow') as FormControl).patchValue({ amount, }); } @@ -232,17 +241,17 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { if (!this.voucher.id) { return true; } - if (this.voucher.posted && this.auth.user.perms.indexOf('edit-posted-vouchers') !== -1) { + if (this.voucher.posted && this.auth.allowed('edit-posted-vouchers')) { return true; } return ( - this.voucher.user.id === this.auth.user.id || - this.auth.user.perms.indexOf("edit-other-user's-vouchers") !== -1 + this.voucher.user.id === (this.auth.user as User).id || + this.auth.allowed("edit-other-user's-vouchers") ); } post() { - this.ser.post(this.voucher.id).subscribe( + this.ser.post(this.voucher.id as string).subscribe( (result) => { this.loadVoucher(result); this.toaster.show('Success', 'Voucher Posted'); @@ -278,7 +287,7 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { } delete() { - this.ser.delete(this.voucher.id).subscribe( + this.ser.delete(this.voucher.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigate(['/journal'], { replaceUrl: true }); @@ -302,25 +311,15 @@ export class JournalComponent implements OnInit, AfterViewInit, OnDestroy { }); } - setupAccountAutocomplete(): 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))), - ); - } - - displayFn(account?: Account): string | undefined { - return account ? account.name : undefined; + displayFn(account?: Account): string { + return account ? account.name : ''; } accountSelected(event: MatAutocompleteSelectedEvent): void { - this.account = event.option.value; - const date = moment(this.form.get('date').value).format('DD-MMM-YYYY'); - this.accountSer.balance(this.account.id, date).subscribe((v) => { + const account = event.option.value; + this.account = account; + const date = moment((this.form.get('date') as FormControl).value).format('DD-MMM-YYYY'); + this.accountSer.balance(account.id as string, date).subscribe((v) => { this.accBal = v; }); } diff --git a/overlord/src/app/ledger/ledger-item.ts b/overlord/src/app/ledger/ledger-item.ts index d3f2282c..c378f0b0 100644 --- a/overlord/src/app/ledger/ledger-item.ts +++ b/overlord/src/app/ledger/ledger-item.ts @@ -9,4 +9,18 @@ export class LedgerItem { running: number; posted: boolean; url: string; + + public constructor(init?: Partial) { + this.date = ''; + this.id = ''; + this.name = ''; + this.type = ''; + this.narration = ''; + this.debit = 0; + this.credit = 0; + this.running = 0; + this.posted = true; + this.url = ''; + Object.assign(this, init); + } } diff --git a/overlord/src/app/ledger/ledger.component.ts b/overlord/src/app/ledger/ledger.component.ts index b3aa396a..66b3d996 100644 --- a/overlord/src/app/ledger/ledger.component.ts +++ b/overlord/src/app/ledger/ledger.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; @@ -25,13 +25,13 @@ export class LedgerComponent implements OnInit, AfterViewInit { @ViewChild('accountElement', { static: true }) accountElement?: ElementRef; @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: LedgerDataSource; + info: Ledger = new Ledger(); + dataSource: LedgerDataSource = new LedgerDataSource(this.info.body); form: FormGroup; - info: Ledger; - selectedRowId: string; - debit: number; - credit: number; - running: number; + selectedRowId = ''; + debit = 0; + credit = 0; + running = 0; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['date', 'particulars', 'type', 'narration', 'debit', 'credit', 'running']; @@ -51,7 +51,7 @@ export class LedgerComponent implements OnInit, AfterViewInit { account: '', }); - this.accounts = this.form.get('account').valueChanges.pipe( + this.accounts = (this.form.get('account') as FormControl).valueChanges.pipe( startWith(null), map((x) => (x !== null && x.length >= 1 ? x : null)), debounceTime(150), @@ -71,18 +71,18 @@ export class LedgerComponent implements OnInit, AfterViewInit { startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(), finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(), }); - this.dataSource = new LedgerDataSource(this.paginator, this.sort, this.info.body); + this.dataSource = new LedgerDataSource(this.info.body, this.paginator, this.sort); }); } ngAfterViewInit() { setTimeout(() => { - this.accountElement.nativeElement.focus(); + if (this.accountElement) this.accountElement.nativeElement.focus(); }, 0); } - displayFn(account?: Account): string | undefined { - return account ? account.name : undefined; + displayFn(account?: Account): string { + return account ? account.name : ''; } calculateTotals() { @@ -124,11 +124,11 @@ export class LedgerComponent implements OnInit, AfterViewInit { getInfo(): Ledger { const formModel = this.form.value; - return { + return new Ledger({ account: formModel.account, startDate: moment(formModel.startDate).format('DD-MMM-YYYY'), finishDate: moment(formModel.finishDate).format('DD-MMM-YYYY'), - }; + }); } exportCsv() { diff --git a/overlord/src/app/ledger/ledger.service.ts b/overlord/src/app/ledger/ledger.service.ts index 04b3e72d..9fe6639f 100644 --- a/overlord/src/app/ledger/ledger.service.ts +++ b/overlord/src/app/ledger/ledger.service.ts @@ -16,7 +16,7 @@ const serviceName = 'LedgerService'; export class LedgerService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - list(id: string, startDate: string, finishDate): Observable { + list(id: string | null, startDate: string | null, finishDate: string | null): Observable { const listUrl = id === null ? url : `${url}/${id}`; const options = { params: new HttpParams() }; if (startDate !== null) { diff --git a/overlord/src/app/ledger/ledger.ts b/overlord/src/app/ledger/ledger.ts index ab94ff32..c4485512 100644 --- a/overlord/src/app/ledger/ledger.ts +++ b/overlord/src/app/ledger/ledger.ts @@ -6,5 +6,13 @@ export class Ledger { startDate: string; finishDate: string; account: Account; - body?: LedgerItem[]; + body: LedgerItem[]; + + public constructor(init?: Partial) { + this.startDate = ''; + this.finishDate = ''; + this.account = new Account(); + this.body = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/net-transactions/net-transactions-item.ts b/overlord/src/app/net-transactions/net-transactions-item.ts index 5df5e683..9350add6 100644 --- a/overlord/src/app/net-transactions/net-transactions-item.ts +++ b/overlord/src/app/net-transactions/net-transactions-item.ts @@ -2,5 +2,13 @@ export class NetTransactionsItem { type: string; name: string; debit: number; - credit: string; + credit: number; + + public constructor(init?: Partial) { + this.type = ''; + this.name = ''; + this.debit = 0; + this.credit = 0; + Object.assign(this, init); + } } diff --git a/overlord/src/app/net-transactions/net-transactions.component.ts b/overlord/src/app/net-transactions/net-transactions.component.ts index 8e80b062..d776321d 100644 --- a/overlord/src/app/net-transactions/net-transactions.component.ts +++ b/overlord/src/app/net-transactions/net-transactions.component.ts @@ -16,10 +16,10 @@ import { NetTransactionsDataSource } from './net-transactions-datasource'; export class NetTransactionsComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: NetTransactionsDataSource; + info: NetTransactions = new NetTransactions(); + dataSource: NetTransactionsDataSource = new NetTransactionsDataSource(this.info.body); form: FormGroup; - info: NetTransactions; - selectedRowId: string; + selectedRowId = ''; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['type', 'name', 'debit', 'credit']; @@ -39,7 +39,7 @@ export class NetTransactionsComponent implements OnInit { startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(), finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(), }); - this.dataSource = new NetTransactionsDataSource(this.paginator, this.sort, this.info.body); + this.dataSource = new NetTransactionsDataSource(this.info.body, this.paginator, this.sort); }); } @@ -56,10 +56,9 @@ export class NetTransactionsComponent implements OnInit { prepareSubmit(): NetTransactions { const formModel = this.form.value; - return { + return new NetTransactions({ startDate: moment(formModel.startDate).format('DD-MMM-YYYY'), finishDate: moment(formModel.finishDate).format('DD-MMM-YYYY'), - body: [], - }; + }); } } diff --git a/overlord/src/app/net-transactions/net-transactions.service.ts b/overlord/src/app/net-transactions/net-transactions.service.ts index 8c110ad0..318d01fb 100644 --- a/overlord/src/app/net-transactions/net-transactions.service.ts +++ b/overlord/src/app/net-transactions/net-transactions.service.ts @@ -16,7 +16,7 @@ const serviceName = 'NetTransactionsService'; export class NetTransactionsService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - list(startDate: string, finishDate): Observable { + list(startDate: string | null, finishDate: string | null): Observable { const startDateWithSlash = startDate ? `/${startDate}` : ''; const finishDateWithSlash = finishDate ? `/${finishDate}` : ''; return >( diff --git a/overlord/src/app/net-transactions/net-transactions.ts b/overlord/src/app/net-transactions/net-transactions.ts index 1cfee68c..f12f6dfe 100644 --- a/overlord/src/app/net-transactions/net-transactions.ts +++ b/overlord/src/app/net-transactions/net-transactions.ts @@ -4,4 +4,11 @@ export class NetTransactions { startDate: string; finishDate: string; body: NetTransactionsItem[]; + + public constructor(init?: Partial) { + this.startDate = ''; + this.finishDate = ''; + this.body = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/payment/payment-dialog.component.ts b/overlord/src/app/payment/payment-dialog.component.ts index 77d30103..1126ae4c 100644 --- a/overlord/src/app/payment/payment-dialog.component.ts +++ b/overlord/src/app/payment/payment-dialog.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Observable, of as observableOf } from 'rxjs'; @@ -17,7 +17,7 @@ import { MathService } from '../shared/math.service'; export class PaymentDialogComponent implements OnInit { accounts: Observable; form: FormGroup; - account: Account; + account: Account = new Account(); accBal: any; constructor( @@ -32,7 +32,14 @@ export class PaymentDialogComponent implements OnInit { amount: '', }); this.accBal = null; - this.setupAccountAutocomplete(); + // Setup Account Autocomplete + this.accounts = (this.form.get('account') as FormControl).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))), + ); } ngOnInit() { @@ -43,24 +50,14 @@ export class PaymentDialogComponent implements OnInit { this.account = this.data.journal.account; } - 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))), - ); - } - - displayFn(account?: Account): string | undefined { - return account ? account.name : undefined; + displayFn(account?: Account): string { + return account ? account.name : ''; } accountSelected(event: MatAutocompleteSelectedEvent): void { - this.account = event.option.value; - this.accountSer.balance(this.account.id, this.data.date).subscribe((v) => { + const account = event.option.value; + this.account = account; + this.accountSer.balance(account.id as string, this.data.date).subscribe((v) => { this.accBal = v; }); } diff --git a/overlord/src/app/payment/payment.component.html b/overlord/src/app/payment/payment.component.html index 59db7fdc..499883f7 100644 --- a/overlord/src/app/payment/payment.component.html +++ b/overlord/src/app/payment/payment.component.html @@ -166,7 +166,7 @@ mat-raised-button (click)="post()" *ngIf="voucher.id" - [disabled]="voucher.posted || auth.user.perms.indexOf('post-vouchers') === -1" + [disabled]="voucher.posted || auth.allowed('post-vouchers')" > {{ voucher.posted ? 'Posted' : 'Post' }} diff --git a/overlord/src/app/payment/payment.component.ts b/overlord/src/app/payment/payment.component.ts index 6fe88cbc..01119ee5 100644 --- a/overlord/src/app/payment/payment.component.ts +++ b/overlord/src/app/payment/payment.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -15,6 +15,7 @@ import { AccountService } from '../core/account.service'; import { DbFile } from '../core/db-file'; import { Journal } from '../core/journal'; import { ToasterService } from '../core/toaster.service'; +import { User } from '../core/user'; import { Voucher } from '../core/voucher'; import { VoucherService } from '../core/voucher.service'; import { ConfirmDialogComponent } from '../shared/confirm-dialog/confirm-dialog.component'; @@ -34,12 +35,12 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild('accountElement', { static: true }) accountElement?: ElementRef; @ViewChild('dateElement', { static: true }) dateElement?: ElementRef; public journalObservable = new BehaviorSubject([]); - dataSource: PaymentDataSource; + dataSource: PaymentDataSource = new PaymentDataSource(this.journalObservable); form: FormGroup; - paymentAccounts: Account[]; - paymentJournal: Journal; - voucher: Voucher; - account: Account; + paymentAccounts: Account[] = []; + paymentJournal: Journal = new Journal(); + voucher: Voucher = new Voucher(); + account: Account | null; accBal: any; displayedColumns = ['account', 'amount', 'action']; @@ -71,8 +72,24 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy { narration: '', }); this.accBal = null; - this.listenToAccountAutocompleteChange(); - this.listenToPaymentAccountChange(); + // Listen to Account Autocomplete Change + this.accounts = ((this.form.get('addRow') as FormControl).get( + 'account', + ) as FormControl).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))), + ); + // Listen to Payment Account Change + (this.form.get('paymentAccount') as FormControl).valueChanges.subscribe((x) => + this.router.navigate([], { + relativeTo: this.route, + queryParams: { a: x }, + replaceUrl: true, + }), + ); } ngOnInit() { @@ -87,7 +104,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy { 'f2', (): boolean => { setTimeout(() => { - this.dateElement.nativeElement.focus(); + if (this.dateElement) this.dateElement.nativeElement.focus(); }, 0); return false; // Prevent bubbling }, @@ -108,11 +125,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy { new Hotkey( 'ctrl+p', (): boolean => { - if ( - this.voucher.id && - !this.voucher.posted && - this.auth.user.perms.indexOf('post-vouchers') !== -1 - ) + if (this.voucher.id && !this.voucher.posted && this.auth.allowed('post-vouchers')) this.post(); return false; // Prevent bubbling }, @@ -148,17 +161,19 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy { focusAccount() { setTimeout(() => { - this.accountElement.nativeElement.focus(); + if (this.accountElement) this.accountElement.nativeElement.focus(); }, 0); } addRow() { - const amount = this.math.parseAmount(this.form.get('addRow').value.amount, 2); + const amount = this.math.parseAmount((this.form.get('addRow') as FormControl).value.amount, 2); const debit = 1; if (this.account === null || amount <= 0) { return; } - const oldFiltered = this.voucher.journals.filter((x) => x.account.id === this.account.id); + const oldFiltered = this.voucher.journals.filter( + (x) => x.account.id === (this.account as Account).id, + ); const old = oldFiltered.length ? oldFiltered[0] : null; if (old && (old.debit === -1 || old.id === this.paymentJournal.id)) { return; @@ -166,27 +181,28 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy { if (old) { old.amount += amount; } else { - this.voucher.journals.push({ - id: null, - debit, - amount, - account: this.account, - costCentre: null, - }); + this.voucher.journals.push( + new Journal({ + debit, + amount, + account: this.account, + costCentre: null, + }), + ); } this.resetAddRow(); this.updateView(); } resetAddRow() { - this.form.get('addRow').reset({ + (this.form.get('addRow') as FormControl).reset({ account: null, amount: null, }); this.account = null; this.accBal = null; setTimeout(() => { - this.accountElement.nativeElement.focus(); + if (this.accountElement) this.accountElement.nativeElement.focus(); }, 0); } @@ -197,7 +213,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy { Math.abs(journals.map((x) => x.amount).reduce((p, c) => p + c, 0)), 2, ); - this.form.get('paymentAmount').setValue(this.paymentJournal.amount); + (this.form.get('paymentAmount') as FormControl).setValue(this.paymentJournal.amount); } editRow(row: Journal) { @@ -205,7 +221,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy { width: '750px', data: { journal: { ...row }, - date: moment(this.form.get('date').value).format('DD-MMM-YYYY'), + date: moment((this.form.get('date') as FormControl).value).format('DD-MMM-YYYY'), }, }); @@ -234,17 +250,17 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy { if (!this.voucher.id) { return true; } - if (this.voucher.posted && this.auth.user.perms.indexOf('edit-posted-vouchers') !== -1) { + if (this.voucher.posted && this.auth.allowed('edit-posted-vouchers')) { return true; } return ( - this.voucher.user.id === this.auth.user.id || - this.auth.user.perms.indexOf("edit-other-user's-vouchers") !== -1 + this.voucher.user.id === (this.auth.user as User).id || + this.auth.allowed("edit-other-user's-vouchers") ); } post() { - this.ser.post(this.voucher.id).subscribe( + this.ser.post(this.voucher.id as string).subscribe( (result) => { this.loadVoucher(result); this.toaster.show('Success', 'Voucher Posted'); @@ -281,7 +297,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy { } delete() { - this.ser.delete(this.voucher.id).subscribe( + this.ser.delete(this.voucher.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigate(['/payment'], { replaceUrl: true }); @@ -305,35 +321,15 @@ 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))), - ); - } - - listenToPaymentAccountChange(): void { - this.form.get('paymentAccount').valueChanges.subscribe((x) => - this.router.navigate([], { - relativeTo: this.route, - queryParams: { a: x }, - replaceUrl: true, - }), - ); - } - - displayFn(account?: Account): string | undefined { - return account ? account.name : undefined; + displayFn(account?: Account): string { + return account ? account.name : ''; } accountSelected(event: MatAutocompleteSelectedEvent): void { - this.account = event.option.value; - const date = moment(this.form.get('date').value).format('DD-MMM-YYYY'); - this.accountSer.balance(this.account.id, date).subscribe((v) => { + const account = event.option.value; + this.account = account; + const date = moment((this.form.get('date') as FormControl).value).format('DD-MMM-YYYY'); + this.accountSer.balance(account.id as string, date).subscribe((v) => { this.accBal = v; }); } diff --git a/overlord/src/app/product-group/product-group-detail/product-group-detail.component.ts b/overlord/src/app/product-group/product-group-detail/product-group-detail.component.ts index f8521511..122ea59b 100644 --- a/overlord/src/app/product-group/product-group-detail/product-group-detail.component.ts +++ b/overlord/src/app/product-group/product-group-detail/product-group-detail.component.ts @@ -14,7 +14,7 @@ import { ProductGroupService } from '../product-group.service'; export class ProductGroupDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; form: FormGroup; - item: ProductGroup; + item: ProductGroup = new ProductGroup(); constructor( private route: ActivatedRoute, @@ -45,7 +45,7 @@ export class ProductGroupDetailComponent implements OnInit, AfterViewInit { ngAfterViewInit() { setTimeout(() => { - this.nameElement.nativeElement.focus(); + if (this.nameElement) this.nameElement.nativeElement.focus(); }, 0); } diff --git a/overlord/src/app/product-group/product-group-list/product-group-list-datasource.ts b/overlord/src/app/product-group/product-group-list/product-group-list-datasource.ts index a680cd5a..62eac5e2 100644 --- a/overlord/src/app/product-group/product-group-list/product-group-list-datasource.ts +++ b/overlord/src/app/product-group/product-group-list/product-group-list-datasource.ts @@ -57,8 +57,6 @@ export class ProductGroupListDataSource extends DataSource { switch (sort.active) { case 'name': return compare(a.name, b.name, isAsc); - case 'id': - return compare(+a.id, +b.id, isAsc); default: return 0; } diff --git a/overlord/src/app/product-group/product-group-list/product-group-list.component.ts b/overlord/src/app/product-group/product-group-list/product-group-list.component.ts index 0e67e840..babceb45 100644 --- a/overlord/src/app/product-group/product-group-list/product-group-list.component.ts +++ b/overlord/src/app/product-group/product-group-list/product-group-list.component.ts @@ -15,8 +15,8 @@ import { ProductGroupListDataSource } from './product-group-list-datasource'; export class ProductGroupListComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: ProductGroupListDataSource; - list: ProductGroup[]; + list: ProductGroup[] = []; + dataSource: ProductGroupListDataSource = new ProductGroupListDataSource(this.list); /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['name', 'isFixture']; @@ -28,6 +28,6 @@ export class ProductGroupListComponent implements OnInit { this.list = data.list; }); - this.dataSource = new ProductGroupListDataSource(this.paginator, this.sort, this.list); + this.dataSource = new ProductGroupListDataSource(this.list, this.paginator, this.sort); } } diff --git a/overlord/src/app/product-group/product-group.service.ts b/overlord/src/app/product-group/product-group.service.ts index 46891492..1f01e6de 100644 --- a/overlord/src/app/product-group/product-group.service.ts +++ b/overlord/src/app/product-group/product-group.service.ts @@ -18,7 +18,7 @@ const serviceName = 'ProductGroupService'; export class ProductGroupService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - get(id: string): Observable { + get(id: string | null): Observable { const getUrl: string = id === null ? `${url}` : `${url}/${id}`; return >( this.http diff --git a/overlord/src/app/product-ledger/product-ledger-item.ts b/overlord/src/app/product-ledger/product-ledger-item.ts index 459470a6..69821e4c 100644 --- a/overlord/src/app/product-ledger/product-ledger-item.ts +++ b/overlord/src/app/product-ledger/product-ledger-item.ts @@ -12,4 +12,21 @@ export class ProductLedgerItem { runningAmount: number; posted: boolean; url: string[]; + + public constructor(init?: Partial) { + this.date = ''; + this.id = ''; + this.name = ''; + this.type = ''; + this.narration = ''; + this.debitQuantity = 0; + this.debitAmount = 0; + this.creditQuantity = 0; + this.creditAmount = 0; + this.runningQuantity = 0; + this.runningAmount = 0; + this.posted = true; + this.url = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/product-ledger/product-ledger.component.ts b/overlord/src/app/product-ledger/product-ledger.component.ts index e991f22e..f50279d5 100644 --- a/overlord/src/app/product-ledger/product-ledger.component.ts +++ b/overlord/src/app/product-ledger/product-ledger.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; @@ -25,16 +25,16 @@ export class ProductLedgerComponent implements OnInit, AfterViewInit { @ViewChild('productElement', { static: true }) productElement?: ElementRef; @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: ProductLedgerDataSource; + info: ProductLedger = new ProductLedger(); + dataSource: ProductLedgerDataSource = new ProductLedgerDataSource(this.info.body); form: FormGroup; - info: ProductLedger; - selectedRowId: string; - debitQuantity: number; - debitAmount: number; - creditQuantity: number; - creditAmount: number; - runningQuantity: number; - runningAmount: number; + selectedRowId = ''; + debitQuantity = 0; + debitAmount = 0; + creditQuantity = 0; + creditAmount = 0; + runningQuantity = 0; + runningAmount = 0; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = [ 'date', @@ -65,7 +65,7 @@ export class ProductLedgerComponent implements OnInit, AfterViewInit { product: '', }); - this.products = this.form.get('product').valueChanges.pipe( + this.products = (this.form.get('product') as FormControl).valueChanges.pipe( startWith(null), map((x) => (x !== null && x.length >= 1 ? x : null)), debounceTime(150), @@ -85,18 +85,18 @@ export class ProductLedgerComponent implements OnInit, AfterViewInit { startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(), finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(), }); - this.dataSource = new ProductLedgerDataSource(this.paginator, this.sort, this.info.body); + this.dataSource = new ProductLedgerDataSource(this.info.body, this.paginator, this.sort); }); } ngAfterViewInit() { setTimeout(() => { - this.productElement.nativeElement.focus(); + if (this.productElement) this.productElement.nativeElement.focus(); }, 0); } - displayFn(product?: Product): string | undefined { - return product ? product.name : undefined; + displayFn(product?: Product): string { + return product ? product.name : ''; } calculateTotals() { @@ -129,7 +129,7 @@ export class ProductLedgerComponent implements OnInit, AfterViewInit { show() { const info = this.getInfo(); - this.router.navigate(['product-ledger', info.product.id], { + this.router.navigate(['product-ledger', (info.product as Product).id], { queryParams: { startDate: info.startDate, finishDate: info.finishDate, @@ -140,11 +140,11 @@ export class ProductLedgerComponent implements OnInit, AfterViewInit { getInfo(): ProductLedger { const formModel = this.form.value; - return { + return new ProductLedger({ product: formModel.product, startDate: moment(formModel.startDate).format('DD-MMM-YYYY'), finishDate: moment(formModel.finishDate).format('DD-MMM-YYYY'), - }; + }); } exportCsv() { diff --git a/overlord/src/app/product-ledger/product-ledger.service.ts b/overlord/src/app/product-ledger/product-ledger.service.ts index e441eb89..aad0ce60 100644 --- a/overlord/src/app/product-ledger/product-ledger.service.ts +++ b/overlord/src/app/product-ledger/product-ledger.service.ts @@ -16,7 +16,11 @@ const serviceName = 'ProductLedgerService'; export class ProductLedgerService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - list(id: string, startDate: string, finishDate): Observable { + list( + id: string | null, + startDate: string | null, + finishDate: string | null, + ): Observable { const listUrl = id === null ? url : `${url}/${id}`; const options = { params: new HttpParams() }; if (startDate !== null) { diff --git a/overlord/src/app/product-ledger/product-ledger.ts b/overlord/src/app/product-ledger/product-ledger.ts index aeb9551e..6e6f31f2 100644 --- a/overlord/src/app/product-ledger/product-ledger.ts +++ b/overlord/src/app/product-ledger/product-ledger.ts @@ -6,5 +6,12 @@ export class ProductLedger { startDate: string; finishDate: string; product?: Product; - body?: ProductLedgerItem[]; + body: ProductLedgerItem[]; + + public constructor(init?: Partial) { + this.startDate = ''; + this.finishDate = ''; + this.body = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/product/product-detail/product-detail.component.ts b/overlord/src/app/product/product-detail/product-detail.component.ts index 270271b8..d6f79e9e 100644 --- a/overlord/src/app/product/product-detail/product-detail.component.ts +++ b/overlord/src/app/product/product-detail/product-detail.component.ts @@ -17,8 +17,8 @@ import { ProductService } from '../product.service'; export class ProductDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; form: FormGroup; - productGroups: ProductGroup[]; - item: Product; + productGroups: ProductGroup[] = []; + item: Product = new Product(); constructor( private route: ActivatedRoute, @@ -73,7 +73,7 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { ngAfterViewInit() { setTimeout(() => { - this.nameElement.nativeElement.focus(); + if (this.nameElement) this.nameElement.nativeElement.focus(); }, 0); } @@ -90,7 +90,7 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { } delete() { - this.ser.delete(this.item.id).subscribe( + this.ser.delete(this.item.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigateByUrl('/products'); diff --git a/overlord/src/app/product/product-list/product-list-datasource.ts b/overlord/src/app/product/product-list/product-list-datasource.ts index cfdf895e..60f61648 100644 --- a/overlord/src/app/product/product-list/product-list-datasource.ts +++ b/overlord/src/app/product/product-list/product-list-datasource.ts @@ -12,11 +12,11 @@ function compare(a: string | number, b: string | number, isAsc: boolean) { return (a < b ? -1 : 1) * (isAsc ? 1 : -1); } export class ProductListDataSource extends DataSource { - private filterValue: string = ""; + private filterValue = ''; constructor( public data: Product[], - private filter: Observable, + private readonly filter: Observable, private paginator?: MatPaginator, private sort?: MatSort, ) { @@ -83,8 +83,6 @@ export class ProductListDataSource extends DataSource { return compare(a.name, b.name, isAsc); case 'productGroup': return compare(a.productGroup.name, b.productGroup.name, isAsc); - case 'id': - return compare(+a.id, +b.id, isAsc); default: return 0; } diff --git a/overlord/src/app/product/product-list/product-list.component.ts b/overlord/src/app/product/product-list/product-list.component.ts index 049e5ce1..4187f362 100644 --- a/overlord/src/app/product/product-list/product-list.component.ts +++ b/overlord/src/app/product/product-list/product-list.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { ActivatedRoute } from '@angular/router'; @@ -20,22 +20,27 @@ export class ProductListComponent implements OnInit, AfterViewInit { @ViewChild('filterElement', { static: true }) filterElement?: ElementRef; @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; + list: Product[] = []; + filter: Observable; dataSource: ProductListDataSource; - filter: Observable; form: FormGroup; - list: Product[]; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ - displayedColumns: string[]; + displayedColumns: string[] = []; // eslint-disable-next-line no-underscore-dangle - private _showExtended: boolean; + private _showExtended = false; constructor(private route: ActivatedRoute, private fb: FormBuilder, private toCsv: ToCsvService) { this.showExtended = false; this.form = this.fb.group({ filter: '', }); - this.filter = this.listenToFilterChange(); + this.filter = (this.form.get('filter') as FormControl).valueChanges.pipe( + startWith(''), + debounceTime(150), + distinctUntilChanged(), + ); + this.dataSource = new ProductListDataSource(this.list, this.filter); } get showExtended(): boolean { @@ -53,24 +58,18 @@ export class ProductListComponent implements OnInit, AfterViewInit { } } - listenToFilterChange() { - return this.form - .get('filter') - .valueChanges.pipe(startWith(''), debounceTime(150), distinctUntilChanged()); - } - ngOnInit() { this.route.data.subscribe((value) => { const data = value as { list: Product[] }; this.list = data.list; }); - this.dataSource = new ProductListDataSource(this.paginator, this.sort, this.filter, this.list); + this.dataSource = new ProductListDataSource(this.list, this.filter, this.paginator, this.sort); } ngAfterViewInit() { setTimeout(() => { - this.filterElement.nativeElement.focus(); + if (this.filterElement) this.filterElement.nativeElement.focus(); }, 0); } diff --git a/overlord/src/app/product/product.service.ts b/overlord/src/app/product/product.service.ts index 2c6326bd..59b752be 100644 --- a/overlord/src/app/product/product.service.ts +++ b/overlord/src/app/product/product.service.ts @@ -17,7 +17,7 @@ const serviceName = 'ProductService'; export class ProductService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - get(id: string): Observable { + get(id: string | null): Observable { const getUrl: string = id === null ? `${url}` : `${url}/${id}`; return >( this.http diff --git a/overlord/src/app/profit-loss/profit-loss-item.ts b/overlord/src/app/profit-loss/profit-loss-item.ts index 01907885..d6610e7b 100644 --- a/overlord/src/app/profit-loss/profit-loss-item.ts +++ b/overlord/src/app/profit-loss/profit-loss-item.ts @@ -3,4 +3,12 @@ export class ProfitLossItem { name: string; amount: number; total: number; + + public constructor(init?: Partial) { + this.group = ''; + this.name = ''; + this.amount = 0; + this.total = 0; + Object.assign(this, init); + } } diff --git a/overlord/src/app/profit-loss/profit-loss.component.ts b/overlord/src/app/profit-loss/profit-loss.component.ts index 60220ac0..f093baa5 100644 --- a/overlord/src/app/profit-loss/profit-loss.component.ts +++ b/overlord/src/app/profit-loss/profit-loss.component.ts @@ -16,10 +16,10 @@ import { ProfitLossDataSource } from './profit-loss-datasource'; export class ProfitLossComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: ProfitLossDataSource; + info: ProfitLoss = new ProfitLoss(); + dataSource: ProfitLossDataSource = new ProfitLossDataSource(this.info.body); form: FormGroup; - info: ProfitLoss; - selectedRowId: string; + selectedRowId = ''; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['group', 'name', 'amount', 'total']; @@ -39,7 +39,7 @@ export class ProfitLossComponent implements OnInit { startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(), finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(), }); - this.dataSource = new ProfitLossDataSource(this.paginator, this.sort, this.info.body); + this.dataSource = new ProfitLossDataSource(this.info.body, this.paginator, this.sort); }); } @@ -56,11 +56,9 @@ export class ProfitLossComponent implements OnInit { prepareSubmit(): ProfitLoss { const formModel = this.form.value; - return { + return new ProfitLoss({ startDate: moment(formModel.startDate).format('DD-MMM-YYYY'), finishDate: moment(formModel.finishDate).format('DD-MMM-YYYY'), - body: [], - footer: { group: null, name: null, amount: null, total: null }, - }; + }); } } diff --git a/overlord/src/app/profit-loss/profit-loss.service.ts b/overlord/src/app/profit-loss/profit-loss.service.ts index 0f03bbad..9d69c248 100644 --- a/overlord/src/app/profit-loss/profit-loss.service.ts +++ b/overlord/src/app/profit-loss/profit-loss.service.ts @@ -16,7 +16,7 @@ const serviceName = 'ProfitLossService'; export class ProfitLossService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - list(startDate: string, finishDate): Observable { + list(startDate: string | null, finishDate: string | null): Observable { const startDateWithSlash = startDate ? `/${startDate}` : ''; const finishDateWithSlash = finishDate ? `/${finishDate}` : ''; return >( diff --git a/overlord/src/app/profit-loss/profit-loss.ts b/overlord/src/app/profit-loss/profit-loss.ts index 96ecc27d..0b3aa81d 100644 --- a/overlord/src/app/profit-loss/profit-loss.ts +++ b/overlord/src/app/profit-loss/profit-loss.ts @@ -5,4 +5,12 @@ export class ProfitLoss { finishDate: string; body: ProfitLossItem[]; footer: ProfitLossItem; + + public constructor(init?: Partial) { + this.startDate = ''; + this.finishDate = ''; + this.body = []; + this.footer = new ProfitLossItem(); + Object.assign(this, init); + } } diff --git a/overlord/src/app/purchase-entries/purchase-entries-item.ts b/overlord/src/app/purchase-entries/purchase-entries-item.ts index 366a765e..4d3c2c50 100644 --- a/overlord/src/app/purchase-entries/purchase-entries-item.ts +++ b/overlord/src/app/purchase-entries/purchase-entries-item.ts @@ -9,4 +9,18 @@ export class PurchaseEntriesItem { tax: number; discount: number; amount: number; + + public constructor(init?: Partial) { + this.id = ''; + this.date = ''; + this.supplier = ''; + this.url = []; + this.product = ''; + this.quantity = 0; + this.rate = 0; + this.tax = 0; + this.discount = 0; + this.amount = 0; + Object.assign(this, init); + } } diff --git a/overlord/src/app/purchase-entries/purchase-entries.component.ts b/overlord/src/app/purchase-entries/purchase-entries.component.ts index 0f0e5a2c..114e6c12 100644 --- a/overlord/src/app/purchase-entries/purchase-entries.component.ts +++ b/overlord/src/app/purchase-entries/purchase-entries.component.ts @@ -16,10 +16,10 @@ import { PurchaseEntriesDataSource } from './purchase-entries-datasource'; export class PurchaseEntriesComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: PurchaseEntriesDataSource; + info: PurchaseEntries = new PurchaseEntries(); + dataSource: PurchaseEntriesDataSource = new PurchaseEntriesDataSource(this.info.body); form: FormGroup; - info: PurchaseEntries; - selectedRowId: string; + selectedRowId = ''; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = [ 'date', @@ -48,7 +48,7 @@ export class PurchaseEntriesComponent implements OnInit { startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(), finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(), }); - this.dataSource = new PurchaseEntriesDataSource(this.paginator, this.sort, this.info.body); + this.dataSource = new PurchaseEntriesDataSource(this.info.body, this.paginator, this.sort); }); } diff --git a/overlord/src/app/purchase-entries/purchase-entries.service.ts b/overlord/src/app/purchase-entries/purchase-entries.service.ts index 0075d717..a9e6a113 100644 --- a/overlord/src/app/purchase-entries/purchase-entries.service.ts +++ b/overlord/src/app/purchase-entries/purchase-entries.service.ts @@ -16,7 +16,7 @@ const serviceName = 'PurchaseEntriesService'; export class PurchaseEntriesService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - list(startDate: string, finishDate): Observable { + list(startDate: string | null, finishDate: string | null): Observable { const startDateWithSlash = startDate ? `/${startDate}` : ''; const finishDateWithSlash = finishDate ? `/${finishDate}` : ''; return >( diff --git a/overlord/src/app/purchase-entries/purchase-entries.ts b/overlord/src/app/purchase-entries/purchase-entries.ts index b96ab1e2..5aecae1b 100644 --- a/overlord/src/app/purchase-entries/purchase-entries.ts +++ b/overlord/src/app/purchase-entries/purchase-entries.ts @@ -4,4 +4,11 @@ export class PurchaseEntries { startDate: string; finishDate: string; body: PurchaseEntriesItem[]; + + public constructor(init?: Partial) { + this.startDate = ''; + this.finishDate = ''; + this.body = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/purchase-return/purchase-return-dialog.component.ts b/overlord/src/app/purchase-return/purchase-return-dialog.component.ts index 32191630..47976223 100644 --- a/overlord/src/app/purchase-return/purchase-return-dialog.component.ts +++ b/overlord/src/app/purchase-return/purchase-return-dialog.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Observable } from 'rxjs'; @@ -17,7 +17,7 @@ import { MathService } from '../shared/math.service'; export class PurchaseReturnDialogComponent implements OnInit { batches: Observable; form: FormGroup; - batch: Batch; + batch: Batch = new Batch(); constructor( public dialogRef: MatDialogRef, @@ -30,7 +30,13 @@ export class PurchaseReturnDialogComponent implements OnInit { batch: '', quantity: '', }); - this.listenToBatchAutocompleteChange(); + // Listen to Batch Autocomplete Change + this.batches = (this.form.get('batch') as FormControl).valueChanges.pipe( + startWith('null'), + debounceTime(150), + distinctUntilChanged(), + switchMap((x) => this.batchSer.autocomplete(this.data.date, x)), + ); } ngOnInit() { @@ -41,18 +47,8 @@ export class PurchaseReturnDialogComponent implements OnInit { this.batch = this.data.inventory.batch; } - listenToBatchAutocompleteChange(): void { - const control = this.form.get('batch'); - this.batches = control.valueChanges.pipe( - startWith('null'), - debounceTime(150), - distinctUntilChanged(), - switchMap((x) => this.batchSer.autocomplete(this.data.date, x)), - ); - } - - displayFn(batch?: Batch): string | undefined { - return batch ? batch.name : undefined; + displayFn(batch?: Batch): string { + return batch ? batch.name : ''; } batchSelected(event: MatAutocompleteSelectedEvent): void { diff --git a/overlord/src/app/purchase-return/purchase-return.component.html b/overlord/src/app/purchase-return/purchase-return.component.html index bac15755..72720197 100644 --- a/overlord/src/app/purchase-return/purchase-return.component.html +++ b/overlord/src/app/purchase-return/purchase-return.component.html @@ -207,7 +207,7 @@ mat-raised-button (click)="post()" *ngIf="voucher.id" - [disabled]="voucher.posted || auth.user.perms.indexOf('post-vouchers') === -1" + [disabled]="voucher.posted || auth.allowed('post-vouchers')" > {{ voucher.posted ? 'Posted' : 'Post' }} diff --git a/overlord/src/app/purchase-return/purchase-return.component.ts b/overlord/src/app/purchase-return/purchase-return.component.ts index 40f072cc..6795b34f 100644 --- a/overlord/src/app/purchase-return/purchase-return.component.ts +++ b/overlord/src/app/purchase-return/purchase-return.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -17,6 +17,7 @@ import { BatchService } from '../core/batch.service'; import { DbFile } from '../core/db-file'; import { Inventory } from '../core/inventory'; import { ToasterService } from '../core/toaster.service'; +import { User } from '../core/user'; import { Voucher } from '../core/voucher'; import { VoucherService } from '../core/voucher.service'; import { ConfirmDialogComponent } from '../shared/confirm-dialog/confirm-dialog.component'; @@ -37,10 +38,10 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy @ViewChild('batchElement', { static: true }) batchElement?: ElementRef; @ViewChild('dateElement', { static: true }) dateElement?: ElementRef; public inventoryObservable = new BehaviorSubject([]); - dataSource: PurchaseReturnDataSource; + dataSource: PurchaseReturnDataSource = new PurchaseReturnDataSource(this.inventoryObservable); form: FormGroup; - voucher: Voucher; - batch: Batch; + voucher: Voucher = new Voucher(); + batch: Batch | null = null; accBal: any; displayedColumns = ['product', 'quantity', 'rate', 'tax', 'discount', 'amount', 'action']; @@ -72,8 +73,25 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy }), narration: '', }); - this.listenToAccountAutocompleteChange(); - this.listenToBatchAutocompleteChange(); + // Listen to Account Autocomplete Change + this.accounts = (this.form.get('account') as FormControl).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))), + ); + // Listen to Batch Autocomplete Change + this.batches = ((this.form.get('addRow') as FormControl).get( + 'batch', + ) as FormControl).valueChanges.pipe( + startWith(''), + debounceTime(150), + distinctUntilChanged(), + switchMap((x) => + this.batchSer.autocomplete(moment(this.form.value.date).format('DD-MMM-YYYY'), x), + ), + ); } ngOnInit() { @@ -87,7 +105,7 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy 'f2', (): boolean => { setTimeout(() => { - this.dateElement.nativeElement.focus(); + if (this.dateElement) this.dateElement.nativeElement.focus(); }, 0); return false; // Prevent bubbling }, @@ -110,11 +128,7 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy new Hotkey( 'ctrl+p', (): boolean => { - if ( - this.voucher.id && - !this.voucher.posted && - this.auth.user.perms.indexOf('post-vouchers') !== -1 - ) { + if (this.voucher.id && !this.voucher.posted && this.auth.allowed('post-vouchers')) { this.post(); } return false; // Prevent bubbling @@ -150,56 +164,54 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy focusAccount() { setTimeout(() => { - this.accountElement.nativeElement.focus(); + if (this.accountElement) this.accountElement.nativeElement.focus(); }, 0); } addRow() { - const formValue = this.form.get('addRow').value; + const formValue = (this.form.get('addRow') as FormControl).value; const quantity = this.math.parseAmount(formValue.quantity, 2); if (this.batch === null || quantity <= 0 || this.batch.quantityRemaining < quantity) { return; } const oldFiltered = this.voucher.inventories.filter( - (x) => x.product.id === this.batch.product.id, + (x) => x.product.id === (this.batch as Batch).product.id, ); if (oldFiltered.length) { this.toaster.show('Danger', 'Product already added'); return; } - this.voucher.inventories.push({ - id: null, - quantity, - rate: this.batch.rate, - tax: this.batch.tax, - discount: this.batch.discount, - amount: quantity * this.batch.rate * (1 + this.batch.tax) * (1 - this.batch.discount), - product: this.batch.product, - batch: this.batch, - }); + this.voucher.inventories.push( + new Inventory({ + quantity, + rate: this.batch.rate, + tax: this.batch.tax, + discount: this.batch.discount, + amount: quantity * this.batch.rate * (1 + this.batch.tax) * (1 - this.batch.discount), + product: this.batch.product, + batch: this.batch, + }), + ); this.resetAddRow(); this.updateView(); } resetAddRow() { - this.form.get('addRow').reset({ + (this.form.get('addRow') as FormControl).reset({ batch: null, quantity: '', }); this.batch = null; setTimeout(() => { - this.batchElement.nativeElement.focus(); + if (this.batchElement) this.batchElement.nativeElement.focus(); }, 0); } updateView() { this.inventoryObservable.next(this.voucher.inventories); - this.form - .get('amount') - .setValue( - round(Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0))), - 2, - ); + (this.form.get('amount') as FormControl).setValue( + round(Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0)), 2), + ); } editRow(row: Inventory) { @@ -236,17 +248,17 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy if (!this.voucher.id) { return true; } - if (this.voucher.posted && this.auth.user.perms.indexOf('edit-posted-vouchers') !== -1) { + if (this.voucher.posted && this.auth.allowed('edit-posted-vouchers')) { return true; } return ( - this.voucher.user.id === this.auth.user.id || - this.auth.user.perms.indexOf("edit-other-user's-vouchers") !== -1 + this.voucher.user.id === (this.auth.user as User).id || + this.auth.allowed("edit-other-user's-vouchers") ); } post() { - this.ser.post(this.voucher.id).subscribe( + this.ser.post(this.voucher.id as string).subscribe( (result) => { this.loadVoucher(result); this.toaster.show('Success', 'Voucher Posted'); @@ -283,7 +295,7 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy } delete() { - this.ser.delete(this.voucher.id).subscribe( + this.ser.delete(this.voucher.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigate(['/purchase-return'], { replaceUrl: true }); @@ -307,35 +319,12 @@ export class PurchaseReturnComponent implements OnInit, AfterViewInit, OnDestroy }); } - listenToAccountAutocompleteChange(): 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))), - ); - } - - listenToBatchAutocompleteChange(): void { - const control = this.form.get('addRow').get('batch'); - this.batches = control.valueChanges.pipe( - startWith(''), - debounceTime(150), - distinctUntilChanged(), - switchMap((x) => - this.batchSer.autocomplete(moment(this.form.value.date).format('DD-MMM-YYYY'), x), - ), - ); - } - - displayFn(item?: Account | Batch): string | undefined { - return item ? item.name : undefined; + displayFn(item?: Account | Batch): string { + return item ? item.name : ''; } accountSelected(event: MatAutocompleteSelectedEvent): void { - this.form.get('account').setValue(event.option.value); + (this.form.get('account') as FormControl).setValue(event.option.value); } batchSelected(event: MatAutocompleteSelectedEvent): void { diff --git a/overlord/src/app/purchase/purchase-dialog.component.ts b/overlord/src/app/purchase/purchase-dialog.component.ts index 28256c1c..dc8dad8d 100644 --- a/overlord/src/app/purchase/purchase-dialog.component.ts +++ b/overlord/src/app/purchase/purchase-dialog.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { round } from 'mathjs'; @@ -18,7 +18,7 @@ import { MathService } from '../shared/math.service'; export class PurchaseDialogComponent implements OnInit { products: Observable; form: FormGroup; - product: Product; + product: Product = new Product(); constructor( public dialogRef: MatDialogRef, @@ -34,7 +34,14 @@ export class PurchaseDialogComponent implements OnInit { tax: '', discount: '', }); - this.listenToProductAutocompleteChange(); + // Listen to Product Autocomplete Change + this.products = (this.form.get('product') as FormControl).valueChanges.pipe( + startWith(null), + map((x) => (x !== null && x.length >= 1 ? x : null)), + debounceTime(150), + distinctUntilChanged(), + switchMap((x) => (x === null ? observableOf([]) : this.productSer.autocomplete(x))), + ); } ngOnInit() { @@ -48,24 +55,13 @@ export class PurchaseDialogComponent implements OnInit { this.product = this.data.inventory.product; } - listenToProductAutocompleteChange(): void { - const control = this.form.get('product'); - this.products = control.valueChanges.pipe( - startWith(null), - map((x) => (x !== null && x.length >= 1 ? x : null)), - debounceTime(150), - distinctUntilChanged(), - switchMap((x) => (x === null ? observableOf([]) : this.productSer.autocomplete(x))), - ); - } - - displayFn(product?: Product): string | undefined { - return product ? product.name : undefined; + displayFn(product?: Product): string { + return product ? product.name : ''; } productSelected(event: MatAutocompleteSelectedEvent): void { this.product = event.option.value; - this.form.get('price').setValue(this.product.price); + (this.form.get('price') as FormControl).setValue(this.product.price); } accept(): void { diff --git a/overlord/src/app/purchase/purchase.component.html b/overlord/src/app/purchase/purchase.component.html index d33002dc..2cf7560f 100644 --- a/overlord/src/app/purchase/purchase.component.html +++ b/overlord/src/app/purchase/purchase.component.html @@ -231,7 +231,7 @@ mat-raised-button (click)="post()" *ngIf="voucher.id" - [disabled]="voucher.posted || auth.user.perms.indexOf('post-vouchers') === -1" + [disabled]="voucher.posted || auth.allowed('post-vouchers')" > {{ voucher.posted ? 'Posted' : 'Post' }} diff --git a/overlord/src/app/purchase/purchase.component.ts b/overlord/src/app/purchase/purchase.component.ts index c16c2ff6..6da83d47 100644 --- a/overlord/src/app/purchase/purchase.component.ts +++ b/overlord/src/app/purchase/purchase.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -16,6 +16,7 @@ import { DbFile } from '../core/db-file'; import { Inventory } from '../core/inventory'; import { Product } from '../core/product'; import { ToasterService } from '../core/toaster.service'; +import { User } from '../core/user'; import { Voucher } from '../core/voucher'; import { VoucherService } from '../core/voucher.service'; import { ProductService } from '../product/product.service'; @@ -37,10 +38,10 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild('productElement', { static: true }) productElement?: ElementRef; @ViewChild('dateElement', { static: true }) dateElement?: ElementRef; public inventoryObservable = new BehaviorSubject([]); - dataSource: PurchaseDataSource; + dataSource: PurchaseDataSource = new PurchaseDataSource(this.inventoryObservable); form: FormGroup; - voucher: Voucher; - product: Product; + voucher: Voucher = new Voucher(); + product: Product | null = null; accBal: any; displayedColumns = ['product', 'quantity', 'rate', 'tax', 'discount', 'amount', 'action']; @@ -76,8 +77,24 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { narration: '', }); this.accBal = null; - this.listenToAccountAutocompleteChange(); - this.listenToProductAutocompleteChange(); + // Listen to Account Autocomplete Change + this.accounts = (this.form.get('account') as FormControl).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))), + ); + // Listen to Product Autocomplete Change + this.products = ((this.form.get('addRow') as FormControl).get( + 'product', + ) as FormControl).valueChanges.pipe( + startWith(null), + map((x) => (x !== null && x.length >= 1 ? x : null)), + debounceTime(150), + distinctUntilChanged(), + switchMap((x) => (x === null ? observableOf([]) : this.productSer.autocomplete(x))), + ); } ngOnInit() { @@ -91,7 +108,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { 'f2', (): boolean => { setTimeout(() => { - this.dateElement.nativeElement.focus(); + if (this.dateElement) this.dateElement.nativeElement.focus(); }, 0); return false; // Prevent bubbling }, @@ -114,11 +131,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { new Hotkey( 'ctrl+p', (): boolean => { - if ( - this.voucher.id && - !this.voucher.posted && - this.auth.user.perms.indexOf('post-vouchers') !== -1 - ) { + if (this.voucher.id && !this.voucher.posted && this.auth.allowed('post-vouchers')) { this.post(); } return false; // Prevent bubbling @@ -136,7 +149,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { this.hotkeys.reset(); } - loadVoucher(voucher) { + loadVoucher(voucher: Voucher) { this.voucher = voucher; this.form.setValue({ date: moment(this.voucher.date, 'DD-MMM-YYYY').toDate(), @@ -157,12 +170,12 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { focusAccount() { setTimeout(() => { - this.accountElement.nativeElement.focus(); + if (this.accountElement) this.accountElement.nativeElement.focus(); }, 0); } addRow() { - const formValue = this.form.get('addRow').value; + const formValue = (this.form.get('addRow') as FormControl).value; const quantity = this.math.parseAmount(formValue.quantity, 2); const price = this.math.parseAmount(formValue.price, 2); const tax = this.math.parseAmount(formValue.tax, 5); @@ -170,27 +183,30 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { if (this.product === null || quantity <= 0 || price <= 0) { return; } - const oldFiltered = this.voucher.inventories.filter((x) => x.product.id === this.product.id); + const oldFiltered = this.voucher.inventories.filter( + (x) => x.product.id === (this.product as Product).id, + ); if (oldFiltered.length) { this.toaster.show('Danger', 'Product already added'); return; } - this.voucher.inventories.push({ - id: null, - quantity, - rate: price, - tax, - discount, - amount: round(quantity * price * (1 + tax) * (1 - discount), 2), - product: this.product, - batch: null, - }); + this.voucher.inventories.push( + new Inventory({ + quantity, + rate: price, + tax, + discount, + amount: round(quantity * price * (1 + tax) * (1 - discount), 2), + product: this.product, + batch: null, + }), + ); this.resetAddRow(); this.updateView(); } resetAddRow() { - this.form.get('addRow').reset({ + (this.form.get('addRow') as FormControl).reset({ product: null, quantity: '', price: '', @@ -199,18 +215,15 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { }); this.product = null; setTimeout(() => { - this.productElement.nativeElement.focus(); + if (this.productElement) this.productElement.nativeElement.focus(); }, 0); } updateView() { this.inventoryObservable.next(this.voucher.inventories); - this.form - .get('amount') - .setValue( - round(Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0))), - 2, - ); + (this.form.get('amount') as FormControl).setValue( + round(Math.abs(this.voucher.inventories.map((x) => x.amount).reduce((p, c) => p + c, 0)), 2), + ); } editRow(row: Inventory) { @@ -244,17 +257,17 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { if (!this.voucher.id) { return true; } - if (this.voucher.posted && this.auth.user.perms.indexOf('edit-posted-vouchers') !== -1) { + if (this.voucher.posted && this.auth.allowed('edit-posted-vouchers')) { return true; } return ( - this.voucher.user.id === this.auth.user.id || - this.auth.user.perms.indexOf("edit-other-user's-vouchers") !== -1 + this.voucher.user.id === (this.auth.user as User).id || + this.auth.allowed("edit-other-user's-vouchers") ); } post() { - this.ser.post(this.voucher.id).subscribe( + this.ser.post(this.voucher.id as string).subscribe( (result) => { this.loadVoucher(result); this.toaster.show('Success', 'Voucher Posted'); @@ -291,7 +304,7 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { } delete() { - this.ser.delete(this.voucher.id).subscribe( + this.ser.delete(this.voucher.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigate(['/purchase'], { replaceUrl: true }); @@ -315,39 +328,18 @@ export class PurchaseComponent implements OnInit, AfterViewInit, OnDestroy { }); } - listenToAccountAutocompleteChange(): 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))), - ); - } - - listenToProductAutocompleteChange(): void { - const control = this.form.get('addRow').get('product'); - this.products = control.valueChanges.pipe( - startWith(null), - map((x) => (x !== null && x.length >= 1 ? x : null)), - debounceTime(150), - distinctUntilChanged(), - switchMap((x) => (x === null ? observableOf([]) : this.productSer.autocomplete(x))), - ); - } - - displayFn(item?: Account | Product): string | undefined { - return item ? item.name : undefined; + displayFn(item?: Account | Product): string { + return item ? item.name : ''; } accountSelected(event: MatAutocompleteSelectedEvent): void { - this.form.get('account').setValue(event.option.value); + (this.form.get('account') as FormControl).setValue(event.option.value); } productSelected(event: MatAutocompleteSelectedEvent): void { - this.product = event.option.value; - this.form.get('addRow').get('price').setValue(this.product.price); + const product = event.option.value; + this.product = product; + ((this.form.get('addRow') as FormControl).get('price') as FormControl).setValue(product.price); } zoomImage(file: DbFile) { diff --git a/overlord/src/app/purchases/purchases-item.ts b/overlord/src/app/purchases/purchases-item.ts index 747a9002..416b6f62 100644 --- a/overlord/src/app/purchases/purchases-item.ts +++ b/overlord/src/app/purchases/purchases-item.ts @@ -4,4 +4,13 @@ export class PurchasesItem { rate: number; amount: number; url: string[]; + + public constructor(init?: Partial) { + this.name = ''; + this.quantity = 0; + this.rate = 0; + this.amount = 0; + this.url = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/purchases/purchases.component.ts b/overlord/src/app/purchases/purchases.component.ts index ed00ddb9..fbe1715d 100644 --- a/overlord/src/app/purchases/purchases.component.ts +++ b/overlord/src/app/purchases/purchases.component.ts @@ -17,10 +17,10 @@ import { PurchasesItem } from './purchases-item'; export class PurchasesComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: PurchasesDataSource; + info: Purchases = new Purchases(); + dataSource: PurchasesDataSource = new PurchasesDataSource(this.info.body); form: FormGroup; - info: Purchases; - selectedRowId: string; + selectedRowId = ''; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['product', 'quantity', 'rate', 'amount']; @@ -40,7 +40,7 @@ export class PurchasesComponent implements OnInit { startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(), finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(), }); - this.dataSource = new PurchasesDataSource(this.paginator, this.sort, this.info.body); + this.dataSource = new PurchasesDataSource(this.info.body, this.paginator, this.sort); }); } diff --git a/overlord/src/app/purchases/purchases.service.ts b/overlord/src/app/purchases/purchases.service.ts index 49942069..33c55b60 100644 --- a/overlord/src/app/purchases/purchases.service.ts +++ b/overlord/src/app/purchases/purchases.service.ts @@ -16,7 +16,7 @@ const serviceName = 'PurchasesService'; export class PurchasesService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - list(startDate: string, finishDate): Observable { + list(startDate: string | null, finishDate: string | null): Observable { const startDateWithSlash = startDate ? `/${startDate}` : ''; const finishDateWithSlash = finishDate ? `/${finishDate}` : ''; return >( diff --git a/overlord/src/app/purchases/purchases.ts b/overlord/src/app/purchases/purchases.ts index d498976f..6a655b09 100644 --- a/overlord/src/app/purchases/purchases.ts +++ b/overlord/src/app/purchases/purchases.ts @@ -5,4 +5,12 @@ export class Purchases { finishDate: string; body: PurchasesItem[]; footer: PurchasesItem; + + public constructor(init?: Partial) { + this.startDate = ''; + this.finishDate = ''; + this.body = []; + this.footer = new PurchasesItem(); + Object.assign(this, init); + } } diff --git a/overlord/src/app/raw-material-cost/raw-material-cost-item.ts b/overlord/src/app/raw-material-cost/raw-material-cost-item.ts index 57d7acf9..efe83232 100644 --- a/overlord/src/app/raw-material-cost/raw-material-cost-item.ts +++ b/overlord/src/app/raw-material-cost/raw-material-cost-item.ts @@ -9,4 +9,9 @@ export class RawMaterialCostItem { quantity?: number; net?: number; gross?: number; + + public constructor(init?: Partial) { + this.name = ''; + Object.assign(this, init); + } } diff --git a/overlord/src/app/raw-material-cost/raw-material-cost.component.ts b/overlord/src/app/raw-material-cost/raw-material-cost.component.ts index 47b77d58..b6a9bd91 100644 --- a/overlord/src/app/raw-material-cost/raw-material-cost.component.ts +++ b/overlord/src/app/raw-material-cost/raw-material-cost.component.ts @@ -4,9 +4,7 @@ import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; -import { Observable } from 'rxjs'; -import { Product } from '../core/product'; import { ToCsvService } from '../shared/to-csv.service'; import { RawMaterialCost } from './raw-material-cost'; @@ -20,22 +18,20 @@ import { RawMaterialCostDataSource } from './raw-material-cost-datasource'; export class RawMaterialCostComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: RawMaterialCostDataSource; + info: RawMaterialCost = new RawMaterialCost(); + dataSource: RawMaterialCostDataSource = new RawMaterialCostDataSource(this.info.body); form: FormGroup; - info: RawMaterialCost; - debitQuantity: number; - debitAmount: number; - creditQuantity: number; - creditAmount: number; - runningQuantity: number; - runningAmount: number; + debitQuantity = 0; + debitAmount = 0; + creditQuantity = 0; + creditAmount = 0; + runningQuantity = 0; + runningAmount = 0; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ - displayedColumns: string[]; + displayedColumns: string[] = []; columnsNoId = ['name', 'issue', 'sale', 'rmc']; columnsId = ['name', 'group', 'quantity', 'net', 'gross']; - products: Observable; - constructor( private route: ActivatedRoute, private router: Router, @@ -58,7 +54,7 @@ export class RawMaterialCostComponent implements OnInit { startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(), finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(), }); - this.dataSource = new RawMaterialCostDataSource(this.paginator, this.sort, this.info.body); + this.dataSource = new RawMaterialCostDataSource(this.info.body, this.paginator, this.sort); }); } @@ -75,10 +71,10 @@ export class RawMaterialCostComponent implements OnInit { prepareSubmit(): RawMaterialCost { const formModel = this.form.value; - return { + return new RawMaterialCost({ startDate: moment(formModel.startDate).format('DD-MMM-YYYY'), finishDate: moment(formModel.finishDate).format('DD-MMM-YYYY'), - }; + }); } exportCsv() { diff --git a/overlord/src/app/raw-material-cost/raw-material-cost.service.ts b/overlord/src/app/raw-material-cost/raw-material-cost.service.ts index cc2954cd..bf9fccd3 100644 --- a/overlord/src/app/raw-material-cost/raw-material-cost.service.ts +++ b/overlord/src/app/raw-material-cost/raw-material-cost.service.ts @@ -16,7 +16,11 @@ const serviceName = 'RawMaterialCostService'; export class RawMaterialCostService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - list(id: string, startDate: string, finishDate): Observable { + list( + id: string | null, + startDate: string | null, + finishDate: string | null, + ): Observable { const options = { params: new HttpParams() }; if (startDate !== null) { options.params = options.params.set('s', startDate); diff --git a/overlord/src/app/raw-material-cost/raw-material-cost.ts b/overlord/src/app/raw-material-cost/raw-material-cost.ts index ad9e2882..71ea49d7 100644 --- a/overlord/src/app/raw-material-cost/raw-material-cost.ts +++ b/overlord/src/app/raw-material-cost/raw-material-cost.ts @@ -4,6 +4,13 @@ export class RawMaterialCost { id?: string; startDate: string; finishDate: string; - body?: RawMaterialCostItem[]; + body: RawMaterialCostItem[]; footer?: RawMaterialCostItem; + + public constructor(init?: Partial) { + this.startDate = ''; + this.finishDate = ''; + this.body = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/receipt/receipt-dialog.component.ts b/overlord/src/app/receipt/receipt-dialog.component.ts index 97b55588..e9028593 100644 --- a/overlord/src/app/receipt/receipt-dialog.component.ts +++ b/overlord/src/app/receipt/receipt-dialog.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Observable, of as observableOf } from 'rxjs'; @@ -17,7 +17,7 @@ import { MathService } from '../shared/math.service'; export class ReceiptDialogComponent implements OnInit { accounts: Observable; form: FormGroup; - account: Account; + account: Account = new Account(); accBal: any; constructor( @@ -32,7 +32,14 @@ export class ReceiptDialogComponent implements OnInit { amount: '', }); this.accBal = null; - this.setupAccountAutocomplete(); + // Setup Account Autocomplete + this.accounts = (this.form.get('account') as FormControl).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))), + ); } ngOnInit() { @@ -43,24 +50,14 @@ export class ReceiptDialogComponent implements OnInit { this.account = this.data.journal.account; } - 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))), - ); - } - - displayFn(account?: Account): string | undefined { - return account ? account.name : undefined; + displayFn(account?: Account): string { + return account ? account.name : ''; } accountSelected(event: MatAutocompleteSelectedEvent): void { - this.account = event.option.value; - this.accountSer.balance(this.account.id, this.data.date).subscribe((v) => { + const account = event.option.value; + this.account = account; + this.accountSer.balance(account.id as string, this.data.date).subscribe((v) => { this.accBal = v; }); } diff --git a/overlord/src/app/receipt/receipt.component.html b/overlord/src/app/receipt/receipt.component.html index ccb9c542..523f66e5 100644 --- a/overlord/src/app/receipt/receipt.component.html +++ b/overlord/src/app/receipt/receipt.component.html @@ -166,7 +166,7 @@ mat-raised-button (click)="post()" *ngIf="voucher.id" - [disabled]="voucher.posted || auth.user.perms.indexOf('post-vouchers') === -1" + [disabled]="voucher.posted || auth.allowed('post-vouchers')" > {{ voucher.posted ? 'Posted' : 'Post' }} diff --git a/overlord/src/app/receipt/receipt.component.ts b/overlord/src/app/receipt/receipt.component.ts index d630f19d..e556848d 100644 --- a/overlord/src/app/receipt/receipt.component.ts +++ b/overlord/src/app/receipt/receipt.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -15,6 +15,7 @@ import { AccountService } from '../core/account.service'; import { DbFile } from '../core/db-file'; import { Journal } from '../core/journal'; import { ToasterService } from '../core/toaster.service'; +import { User } from '../core/user'; import { Voucher } from '../core/voucher'; import { VoucherService } from '../core/voucher.service'; import { ConfirmDialogComponent } from '../shared/confirm-dialog/confirm-dialog.component'; @@ -34,12 +35,12 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild('accountElement', { static: true }) accountElement?: ElementRef; @ViewChild('dateElement', { static: true }) dateElement?: ElementRef; public journalObservable = new BehaviorSubject([]); - dataSource: ReceiptDataSource; + dataSource: ReceiptDataSource = new ReceiptDataSource(this.journalObservable); form: FormGroup; - receiptAccounts: Account[]; - receiptJournal: Journal; - voucher: Voucher; - account: Account; + receiptAccounts: Account[] = []; + receiptJournal: Journal = new Journal(); + voucher: Voucher = new Voucher(); + account: Account | null = null; accBal: any; displayedColumns = ['account', 'amount', 'action']; @@ -59,7 +60,6 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy { private ser: VoucherService, private accountSer: AccountService, ) { - this.account = null; this.form = this.fb.group({ date: '', receiptAccount: '', @@ -71,8 +71,24 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy { narration: '', }); this.accBal = null; - this.listenToAccountAutocompleteChange(); - this.listenToReceiptAccountChange(); + // Listen to Account Autocomplete Change + this.accounts = ((this.form.get('addRow') as FormControl).get( + 'account', + ) as FormControl).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))), + ); + // Listen to Receipt Account Change + (this.form.get('receiptAccount') as FormControl).valueChanges.subscribe((x) => + this.router.navigate([], { + relativeTo: this.route, + queryParams: { a: x }, + replaceUrl: true, + }), + ); } ngOnInit() { @@ -87,7 +103,7 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy { 'f2', (): boolean => { setTimeout(() => { - this.dateElement.nativeElement.focus(); + if (this.dateElement) this.dateElement.nativeElement.focus(); }, 0); return false; // Prevent bubbling }, @@ -110,11 +126,7 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy { new Hotkey( 'ctrl+p', (): boolean => { - if ( - this.voucher.id && - !this.voucher.posted && - this.auth.user.perms.indexOf('post-vouchers') !== -1 - ) { + if (this.voucher.id && !this.voucher.posted && this.auth.allowed('post-vouchers')) { this.post(); } return false; // Prevent bubbling @@ -151,17 +163,19 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy { focusAccount() { setTimeout(() => { - this.accountElement.nativeElement.focus(); + if (this.accountElement) this.accountElement.nativeElement.focus(); }, 0); } addRow() { - const amount = this.math.parseAmount(this.form.get('addRow').value.amount, 2); + const amount = this.math.parseAmount((this.form.get('addRow') as FormControl).value.amount, 2); const debit = -1; if (this.account === null || amount <= 0) { return; } - const oldFiltered = this.voucher.journals.filter((x) => x.account.id === this.account.id); + const oldFiltered = this.voucher.journals.filter( + (x) => x.account.id === (this.account as Account).id, + ); const old = oldFiltered.length ? oldFiltered[0] : null; if (old && (old.debit === 1 || old.id === this.receiptJournal.id)) { return; @@ -169,27 +183,28 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy { if (old) { old.amount += amount; } else { - this.voucher.journals.push({ - id: null, - debit, - amount, - account: this.account, - costCentre: null, - }); + this.voucher.journals.push( + new Journal({ + debit, + amount, + account: this.account, + costCentre: null, + }), + ); } this.resetAddRow(); this.updateView(); } resetAddRow() { - this.form.get('addRow').reset({ + (this.form.get('addRow') as FormControl).reset({ account: null, amount: null, }); this.account = null; this.accBal = null; setTimeout(() => { - this.accountElement.nativeElement.focus(); + if (this.accountElement) this.accountElement.nativeElement.focus(); }, 0); } @@ -200,7 +215,7 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy { Math.abs(journals.map((x) => x.amount).reduce((p, c) => p + c, 0)), 2, ); - this.form.get('receiptAmount').setValue(this.receiptJournal.amount); + (this.form.get('receiptAmount') as FormControl).setValue(this.receiptJournal.amount); } editRow(row: Journal) { @@ -208,7 +223,7 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy { width: '750px', data: { journal: { ...row }, - date: moment(this.form.get('date').value).format('DD-MMM-YYYY'), + date: moment((this.form.get('date') as FormControl).value).format('DD-MMM-YYYY'), }, }); @@ -237,17 +252,17 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy { if (!this.voucher.id) { return true; } - if (this.voucher.posted && this.auth.user.perms.indexOf('edit-posted-vouchers') !== -1) { + if (this.voucher.posted && this.auth.allowed('edit-posted-vouchers')) { return true; } return ( - this.voucher.user.id === this.auth.user.id || - this.auth.user.perms.indexOf("edit-other-user's-vouchers") !== -1 + this.voucher.user.id === (this.auth.user as User).id || + this.auth.allowed("edit-other-user's-vouchers") ); } post() { - this.ser.post(this.voucher.id).subscribe( + this.ser.post(this.voucher.id as string).subscribe( (result) => { this.loadVoucher(result); this.toaster.show('Success', 'Voucher Posted'); @@ -284,7 +299,7 @@ export class ReceiptComponent implements OnInit, AfterViewInit, OnDestroy { } delete() { - this.ser.delete(this.voucher.id).subscribe( + this.ser.delete(this.voucher.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigate(['/receipt'], { replaceUrl: true }); @@ -308,35 +323,15 @@ export class ReceiptComponent 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))), - ); - } - - listenToReceiptAccountChange(): void { - this.form.get('receiptAccount').valueChanges.subscribe((x) => - this.router.navigate([], { - relativeTo: this.route, - queryParams: { a: x }, - replaceUrl: true, - }), - ); - } - - displayFn(account?: Account): string | undefined { - return account ? account.name : undefined; + displayFn(account?: Account): string { + return account ? account.name : ''; } accountSelected(event: MatAutocompleteSelectedEvent): void { - this.account = event.option.value; - const date = moment(this.form.get('date').value).format('DD-MMM-YYYY'); - this.accountSer.balance(this.account.id, date).subscribe((v) => { + const account = event.option.value; + this.account = account; + const date = moment((this.form.get('date') as FormControl).value).format('DD-MMM-YYYY'); + this.accountSer.balance(account.id as string, date).subscribe((v) => { this.accBal = v; }); } diff --git a/overlord/src/app/role/permission.ts b/overlord/src/app/role/permission.ts index bdbcb732..fe58efd9 100644 --- a/overlord/src/app/role/permission.ts +++ b/overlord/src/app/role/permission.ts @@ -1,5 +1,11 @@ export class Permission { - id: string; + id: string | undefined; name: string; enabled: boolean; + + public constructor(init?: Partial) { + this.name = ''; + this.enabled = true; + Object.assign(this, init); + } } diff --git a/overlord/src/app/role/role-detail/role-detail.component.ts b/overlord/src/app/role/role-detail/role-detail.component.ts index 6810259b..f848cd3b 100644 --- a/overlord/src/app/role/role-detail/role-detail.component.ts +++ b/overlord/src/app/role/role-detail/role-detail.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { FormArray, FormBuilder, FormGroup } from '@angular/forms'; +import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -16,7 +16,7 @@ import { RoleService } from '../role.service'; export class RoleDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; form: FormGroup; - item: Role; + item: Role = new Role(); constructor( private route: ActivatedRoute, @@ -37,7 +37,7 @@ export class RoleDetailComponent implements OnInit, AfterViewInit { const data = value as { item: Role }; this.item = data.item; - this.form.get('name').setValue(this.item.name); + (this.form.get('name') as FormControl).setValue(this.item.name); this.form.setControl( 'permissions', this.fb.array( @@ -53,7 +53,7 @@ export class RoleDetailComponent implements OnInit, AfterViewInit { ngAfterViewInit() { setTimeout(() => { - this.nameElement.nativeElement.focus(); + if (this.nameElement) this.nameElement.nativeElement.focus(); }, 0); } @@ -70,7 +70,7 @@ export class RoleDetailComponent implements OnInit, AfterViewInit { } delete() { - this.ser.delete(this.item.id).subscribe( + this.ser.delete(this.item.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigateByUrl('/roles'); diff --git a/overlord/src/app/role/role-list/role-list-datasource.ts b/overlord/src/app/role/role-list/role-list-datasource.ts index 4cb150e2..491b8f29 100644 --- a/overlord/src/app/role/role-list/role-list-datasource.ts +++ b/overlord/src/app/role/role-list/role-list-datasource.ts @@ -51,8 +51,6 @@ export class RoleListDatasource extends DataSource { switch (sort.active) { case 'name': return compare(a.name, b.name, isAsc); - case 'id': - return compare(+a.id, +b.id, isAsc); default: return 0; } diff --git a/overlord/src/app/role/role-list/role-list.component.ts b/overlord/src/app/role/role-list/role-list.component.ts index 08cb816a..23952c1a 100644 --- a/overlord/src/app/role/role-list/role-list.component.ts +++ b/overlord/src/app/role/role-list/role-list.component.ts @@ -15,8 +15,8 @@ import { RoleListDatasource } from './role-list-datasource'; export class RoleListComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: RoleListDatasource; - list: Role[]; + list: Role[] = []; + dataSource: RoleListDatasource = new RoleListDatasource(this.list); /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['name', 'permissions']; @@ -28,6 +28,6 @@ export class RoleListComponent implements OnInit { this.list = data.list; }); - this.dataSource = new RoleListDatasource(this.paginator, this.sort, this.list); + this.dataSource = new RoleListDatasource(this.list, this.paginator, this.sort); } } diff --git a/overlord/src/app/role/role.service.ts b/overlord/src/app/role/role.service.ts index aec91e9b..3fb95af3 100644 --- a/overlord/src/app/role/role.service.ts +++ b/overlord/src/app/role/role.service.ts @@ -19,7 +19,7 @@ const serviceName = 'RoleService'; export class RoleService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - get(id: string): Observable { + get(id: string | null): Observable { const getUrl: string = id === null ? `${url}` : `${url}/${id}`; return >( this.http diff --git a/overlord/src/app/role/role.ts b/overlord/src/app/role/role.ts index 9f5033e6..648cb8fb 100644 --- a/overlord/src/app/role/role.ts +++ b/overlord/src/app/role/role.ts @@ -1,7 +1,13 @@ import { Permission } from './permission'; export class Role { - id: string; + id: string | undefined; name: string; permissions: Permission[]; + + public constructor(init?: Partial) { + this.name = ''; + this.permissions = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/settings/settings.component.html b/overlord/src/app/settings/settings.component.html index 97a91aa5..bc5177ff 100644 --- a/overlord/src/app/settings/settings.component.html +++ b/overlord/src/app/settings/settings.component.html @@ -219,5 +219,5 @@
-

Backend: v{{ auth.user.ver }} / Frontend: v{{ version }}

+

Backend: v{{ auth.user?.ver }} / Frontend: v{{ version }}

diff --git a/overlord/src/app/settings/settings.component.ts b/overlord/src/app/settings/settings.component.ts index c0ec023e..af6b7305 100644 --- a/overlord/src/app/settings/settings.component.ts +++ b/overlord/src/app/settings/settings.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -28,7 +28,7 @@ export class SettingsComponent implements OnInit { rebaseDataForm: FormGroup; resetStockForm: FormGroup; - product: Product; + product: Product = new Product(); products: Observable; maintenance: any; @@ -75,8 +75,14 @@ export class SettingsComponent implements OnInit { lockNewer: false, newerRolling: false, }; - this.listenToProductChanges(); - + // Listen to Product Changes + this.products = (this.resetStockForm.get('product') as FormControl).valueChanges.pipe( + startWith(''), + map((x) => (x !== null && x.length >= 1 ? x : null)), + debounceTime(150), + distinctUntilChanged(), + switchMap((x) => (x === null ? observableOf([]) : this.productSer.autocomplete(x))), + ); this.maintenance = { enabled: false, user: '' }; this.version = environment.version; } @@ -101,16 +107,16 @@ export class SettingsComponent implements OnInit { } listenToLockForm() { - this.lockDatesForm.get('lockOlder').valueChanges.subscribe((x) => { + (this.lockDatesForm.get('lockOlder') as FormControl).valueChanges.subscribe((x) => { this.lockInformation.lockOlder = x; }); - this.lockDatesForm.get('lockNewer').valueChanges.subscribe((x) => { + (this.lockDatesForm.get('lockNewer') as FormControl).valueChanges.subscribe((x) => { this.lockInformation.lockNewer = x; }); - this.lockDatesForm.get('olderRolling').valueChanges.subscribe((x) => { + (this.lockDatesForm.get('olderRolling') as FormControl).valueChanges.subscribe((x) => { this.lockInformation.olderRolling = x; }); - this.lockDatesForm.get('newerRolling').valueChanges.subscribe((x) => { + (this.lockDatesForm.get('newerRolling') as FormControl).valueChanges.subscribe((x) => { this.lockInformation.newerRolling = x; }); } @@ -123,20 +129,20 @@ export class SettingsComponent implements OnInit { setLockDates() { if (this.lockInformation.lockOlder && this.lockInformation.olderRolling) { - this.lockInformation.olderDays = +this.lockDatesForm.get('olderDays').value; + this.lockInformation.olderDays = +(this.lockDatesForm.get('olderDays') as FormControl).value; } if (this.lockInformation.lockOlder && !this.lockInformation.olderRolling) { - this.lockInformation.olderDate = moment(this.lockDatesForm.get('olderDate').value).format( - 'DD-MMM-YYYY', - ); + this.lockInformation.olderDate = moment( + (this.lockDatesForm.get('olderDate') as FormControl).value, + ).format('DD-MMM-YYYY'); } if (this.lockInformation.lockNewer && this.lockInformation.newerRolling) { - this.lockInformation.newerDays = +this.lockDatesForm.get('newerDays').value; + this.lockInformation.newerDays = +(this.lockDatesForm.get('newerDays') as FormControl).value; } if (this.lockInformation.lockOlder && !this.lockInformation.newerRolling) { - this.lockInformation.newerDate = moment(this.lockDatesForm.get('newerDate').value).format( - 'DD-MMM-YYYY', - ); + this.lockInformation.newerDate = moment( + (this.lockDatesForm.get('newerDate') as FormControl).value, + ).format('DD-MMM-YYYY'); } this.ser.setLockInformation(this.lockInformation).subscribe( (result) => { @@ -150,7 +156,9 @@ export class SettingsComponent implements OnInit { } confirmRebase(): void { - const rebaseDate = moment(this.rebaseDataForm.get('date').value).format('DD-MMM-YYYY'); + const rebaseDate = moment((this.rebaseDataForm.get('date') as FormControl).value).format( + 'DD-MMM-YYYY', + ); const dialogRef = this.dialog.open(ConfirmDialogComponent, { width: '250px', data: { @@ -178,18 +186,8 @@ export class SettingsComponent implements OnInit { ); } - listenToProductChanges() { - this.products = this.resetStockForm.get('product').valueChanges.pipe( - startWith(''), - map((x) => (x !== null && x.length >= 1 ? x : null)), - debounceTime(150), - distinctUntilChanged(), - switchMap((x) => (x === null ? observableOf([]) : this.productSer.autocomplete(x))), - ); - } - - displayFn(product?: Product): string | undefined { - return product ? product.name : undefined; + displayFn(product?: Product): string { + return product ? product.name : ''; } selectedProduct(event: MatAutocompleteSelectedEvent): void { @@ -197,9 +195,13 @@ export class SettingsComponent implements OnInit { } confirmResetStock(): void { - const resetDate = moment(this.resetStockForm.get('resetDate').value).format('DD-MMM-YYYY'); - const stockDate = moment(this.resetStockForm.get('stockDate').value).format('DD-MMM-YYYY'); - const quantity = +this.resetStockForm.get('quantity').value; + const resetDate = moment((this.resetStockForm.get('resetDate') as FormControl).value).format( + 'DD-MMM-YYYY', + ); + const stockDate = moment((this.resetStockForm.get('stockDate') as FormControl).value).format( + 'DD-MMM-YYYY', + ); + const quantity = +(this.resetStockForm.get('quantity') as FormControl).value; const dialogRef = this.dialog.open(ConfirmDialogComponent, { width: '250px', data: { diff --git a/overlord/src/app/settings/settings.service.ts b/overlord/src/app/settings/settings.service.ts index b034cad1..2f655aac 100644 --- a/overlord/src/app/settings/settings.service.ts +++ b/overlord/src/app/settings/settings.service.ts @@ -46,8 +46,8 @@ export class SettingsService { quantity: number, ): Observable { const url = '/api/reset-stock'; - return this.http - .post(`${url}/${product.id}`, { stockDate, resetDate, quantity }) + return >this.http + .post(`${url}/${product.id}`, { stockDate, resetDate, quantity }) .pipe(catchError(this.log.handleError(serviceName, 'resetStock'))); } diff --git a/overlord/src/app/shared/accounting.pipe.ts b/overlord/src/app/shared/accounting.pipe.ts index 03b73855..be29e6c0 100644 --- a/overlord/src/app/shared/accounting.pipe.ts +++ b/overlord/src/app/shared/accounting.pipe.ts @@ -4,7 +4,7 @@ import { Pipe, PipeTransform } from '@angular/core'; name: 'accounting', }) export class AccountingPipe implements PipeTransform { - transform(value: string): string { + transform(value: string | null): string { if (value === null) { return ''; } diff --git a/overlord/src/app/shared/cookie.service.ts b/overlord/src/app/shared/cookie.service.ts index 44fb3065..8faac0da 100644 --- a/overlord/src/app/shared/cookie.service.ts +++ b/overlord/src/app/shared/cookie.service.ts @@ -20,7 +20,7 @@ export class CookieService { return ''; } - public deleteCookie(name) { + public deleteCookie(name: string) { this.setCookie(name, '', -1); } diff --git a/overlord/src/app/shared/image.service.ts b/overlord/src/app/shared/image.service.ts index b949fb96..bc99d7c6 100644 --- a/overlord/src/app/shared/image.service.ts +++ b/overlord/src/app/shared/image.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { fromEvent, of, zip } from 'rxjs'; import { map } from 'rxjs/operators'; +import { DbFile } from '../core/db-file'; import { Voucher } from '../core/voucher'; @Injectable({ @@ -9,9 +10,9 @@ import { Voucher } from '../core/voucher'; }) export class ImageService { // eslint-disable-next-line class-methods-use-this - resizeImage(image, MAX_WIDTH, MAX_HEIGHT) { + resizeImage(image: string, MAX_WIDTH: number, MAX_HEIGHT: number) { const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; const img = new Image(); const ex = fromEvent(img, 'load').pipe( map(() => { @@ -36,8 +37,8 @@ export class ImageService { return ex; } - detectFiles(event, voucher: Voucher) { - const { files } = event.target; + detectFiles(event: Event, voucher: Voucher) { + const { files } = event.target as HTMLInputElement; if (!files) { return; } @@ -49,7 +50,9 @@ export class ImageService { of(e.target.result), this.resizeImage(e.target.result, 100, 150), this.resizeImage(e.target.result, 825, 1170), - ).subscribe((val) => voucher.files.push({ id: null, thumbnail: val[1], resized: val[2] })); + ).subscribe((val) => + voucher.files.push(new DbFile({ thumbnail: val[1], resized: val[2] })), + ); }; reader.readAsDataURL(file); } diff --git a/overlord/src/app/shared/to-csv.service.ts b/overlord/src/app/shared/to-csv.service.ts index 5d080fdf..3fcfe657 100644 --- a/overlord/src/app/shared/to-csv.service.ts +++ b/overlord/src/app/shared/to-csv.service.ts @@ -7,7 +7,7 @@ export class ToCsvService { // eslint-disable-next-line class-methods-use-this toCsv(headers: any, data: any[]): string { const header = Object.keys(headers); - const replacer = (key, value) => (value === null ? '' : value); + const replacer = (key: string, value: string | number | null) => (value === null ? '' : value); const csv = data.map((row) => header.map((fieldName) => JSON.stringify(row[headers[fieldName]], replacer)).join(','), ); diff --git a/overlord/src/app/stock-movement/stock-movement-item.ts b/overlord/src/app/stock-movement/stock-movement-item.ts index e9bb6407..47f9955c 100644 --- a/overlord/src/app/stock-movement/stock-movement-item.ts +++ b/overlord/src/app/stock-movement/stock-movement-item.ts @@ -7,4 +7,16 @@ export class StockMovementItem { issue: number; closing: number; url: string[]; + + public constructor(init?: Partial) { + this.id = ''; + this.group = ''; + this.name = ''; + this.opening = 0; + this.purchase = 0; + this.issue = 0; + this.closing = 0; + this.url = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/stock-movement/stock-movement.component.ts b/overlord/src/app/stock-movement/stock-movement.component.ts index 42514f60..c3e43e82 100644 --- a/overlord/src/app/stock-movement/stock-movement.component.ts +++ b/overlord/src/app/stock-movement/stock-movement.component.ts @@ -16,10 +16,10 @@ import { StockMovementDataSource } from './stock-movement-datasource'; export class StockMovementComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; @ViewChild(MatSort, { static: true }) sort?: MatSort; - dataSource: StockMovementDataSource; + info: StockMovement = new StockMovement(); + dataSource: StockMovementDataSource = new StockMovementDataSource(this.info.body); form: FormGroup; - info: StockMovement; - selectedRowId: string; + selectedRowId = ''; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['group', 'name', 'opening', 'purchase', 'issue', 'closing']; @@ -39,7 +39,7 @@ export class StockMovementComponent implements OnInit { startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(), finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(), }); - this.dataSource = new StockMovementDataSource(this.paginator, this.sort, this.info.body); + this.dataSource = new StockMovementDataSource(this.info.body, this.paginator, this.sort); }); } diff --git a/overlord/src/app/stock-movement/stock-movement.service.ts b/overlord/src/app/stock-movement/stock-movement.service.ts index 965326e9..38bcafd0 100644 --- a/overlord/src/app/stock-movement/stock-movement.service.ts +++ b/overlord/src/app/stock-movement/stock-movement.service.ts @@ -16,7 +16,7 @@ const serviceName = 'StockMovementService'; export class StockMovementService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - list(startDate: string, finishDate): Observable { + list(startDate: string | null, finishDate: string | null): Observable { const startDateWithSlash = startDate ? `/${startDate}` : ''; const finishDateWishSlash = finishDate ? `/${finishDate}` : ''; return >( diff --git a/overlord/src/app/stock-movement/stock-movement.ts b/overlord/src/app/stock-movement/stock-movement.ts index 743a5bcd..d81cf21d 100644 --- a/overlord/src/app/stock-movement/stock-movement.ts +++ b/overlord/src/app/stock-movement/stock-movement.ts @@ -4,4 +4,11 @@ export class StockMovement { startDate: string; finishDate: string; body: StockMovementItem[]; + + public constructor(init?: Partial) { + this.startDate = ''; + this.finishDate = ''; + this.body = []; + Object.assign(this, init); + } } diff --git a/overlord/src/app/trial-balance/trial-balance-item.ts b/overlord/src/app/trial-balance/trial-balance-item.ts index 62c3fcee..5e4e6263 100644 --- a/overlord/src/app/trial-balance/trial-balance-item.ts +++ b/overlord/src/app/trial-balance/trial-balance-item.ts @@ -3,4 +3,12 @@ export class TrialBalanceItem { name: string; debit: number; credit: number; + + public constructor(init?: Partial) { + this.type = ''; + this.name = ''; + this.debit = 0; + this.credit = 0; + Object.assign(this, init); + } } diff --git a/overlord/src/app/trial-balance/trial-balance.service.ts b/overlord/src/app/trial-balance/trial-balance.service.ts index 6e764454..d36615cf 100644 --- a/overlord/src/app/trial-balance/trial-balance.service.ts +++ b/overlord/src/app/trial-balance/trial-balance.service.ts @@ -16,7 +16,7 @@ const serviceName = 'TrialBalanceService'; export class TrialBalanceService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - list(date: string): Observable { + list(date: string | null): Observable { const listUrl = date === null ? url : `${url}/${date}`; return >( this.http diff --git a/overlord/src/app/trial-balance/trial-balance.ts b/overlord/src/app/trial-balance/trial-balance.ts index 0b68dcdd..28b503b2 100644 --- a/overlord/src/app/trial-balance/trial-balance.ts +++ b/overlord/src/app/trial-balance/trial-balance.ts @@ -5,7 +5,7 @@ export class TrialBalance { body: TrialBalanceItem[]; public constructor(init?: Partial) { - this.date = ""; + this.date = ''; this.body = []; Object.assign(this, init); } diff --git a/overlord/src/app/unposted/unposted.component.ts b/overlord/src/app/unposted/unposted.component.ts index afa3f771..855058ec 100644 --- a/overlord/src/app/unposted/unposted.component.ts +++ b/overlord/src/app/unposted/unposted.component.ts @@ -13,10 +13,10 @@ import { UnpostedService } from './unposted.service'; styleUrls: ['./unposted.component.css'], }) export class UnpostedComponent implements OnInit { - @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; - @ViewChild(MatSort, { static: true }) sort: MatSort; - dataSource: UnpostedDataSource; - info: Unposted[]; + @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator; + @ViewChild(MatSort, { static: true }) sort?: MatSort; + info: Unposted[] = []; + dataSource: UnpostedDataSource = new UnpostedDataSource(this.info); /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = [ 'date', @@ -35,10 +35,12 @@ export class UnpostedComponent implements OnInit { ) {} ngOnInit() { - this.route.data.subscribe((data: { info: Unposted[] }) => { + this.route.data.subscribe((value) => { + const data = value as { info: Unposted[] }; + this.info = data.info; }); - this.dataSource = new UnpostedDataSource(this.paginator, this.sort, this.info); + this.dataSource = new UnpostedDataSource(this.info, this.paginator, this.sort); } refresh() { diff --git a/overlord/src/app/unposted/unposted.ts b/overlord/src/app/unposted/unposted.ts index 286f64f9..c1f35f73 100644 --- a/overlord/src/app/unposted/unposted.ts +++ b/overlord/src/app/unposted/unposted.ts @@ -8,4 +8,17 @@ export class Unposted { debitAmount: number; creditName: string; creditAmount: number; + + public constructor(init?: Partial) { + this.id = ''; + this.date = ''; + this.url = []; + this.type = ''; + this.narration = ''; + this.debitName = ''; + this.debitAmount = 0; + this.creditName = ''; + this.creditAmount = 0; + Object.assign(this, init); + } } diff --git a/overlord/src/app/user/user-detail/user-detail.component.ts b/overlord/src/app/user/user-detail/user-detail.component.ts index b53096c4..3a3342d6 100644 --- a/overlord/src/app/user/user-detail/user-detail.component.ts +++ b/overlord/src/app/user/user-detail/user-detail.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { FormArray, FormBuilder, FormGroup } from '@angular/forms'; +import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -14,9 +14,9 @@ import { UserService } from '../user.service'; styleUrls: ['./user-detail.component.css'], }) export class UserDetailComponent implements OnInit, AfterViewInit { - @ViewChild('nameElement', { static: true }) nameElement: ElementRef; + @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; form: FormGroup; - item: User; + item: User = new User(); hide: boolean; constructor( @@ -46,9 +46,9 @@ export class UserDetailComponent implements OnInit, AfterViewInit { showItem(item: User) { this.item = item; - this.form.get('name').setValue(item.name); - this.form.get('password').setValue(''); - this.form.get('lockedOut').setValue(item.lockedOut); + (this.form.get('name') as FormControl).setValue(item.name); + (this.form.get('password') as FormControl).setValue(''); + (this.form.get('lockedOut') as FormControl).setValue(item.lockedOut); this.form.setControl( 'roles', this.fb.array( @@ -63,7 +63,7 @@ export class UserDetailComponent implements OnInit, AfterViewInit { ngAfterViewInit() { setTimeout(() => { - this.nameElement.nativeElement.focus(); + if (this.nameElement) this.nameElement.nativeElement.focus(); }, 0); } @@ -92,7 +92,7 @@ export class UserDetailComponent implements OnInit, AfterViewInit { } delete() { - this.ser.delete(this.item.id).subscribe( + this.ser.delete(this.item.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigateByUrl('/users'); diff --git a/overlord/src/app/user/user-list/user-list.component.ts b/overlord/src/app/user/user-list/user-list.component.ts index 0840d87c..9538e86e 100644 --- a/overlord/src/app/user/user-list/user-list.component.ts +++ b/overlord/src/app/user/user-list/user-list.component.ts @@ -15,8 +15,8 @@ import { UserListDataSource } from './user-list-datasource'; export class UserListComponent implements OnInit { @ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator = undefined; @ViewChild(MatSort, { static: true }) sort?: MatSort = undefined; - dataSource: UserListDataSource; - list: User[]; + list: User[] = []; + dataSource: UserListDataSource = new UserListDataSource(this.list); /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['name', 'lockedOut', 'groups']; @@ -27,6 +27,6 @@ export class UserListComponent implements OnInit { const data = value as { list: User[] }; this.list = data.list; }); - this.dataSource = new UserListDataSource(this.paginator, this.sort, this.list); + this.dataSource = new UserListDataSource(this.list, this.paginator, this.sort); } } diff --git a/overlord/src/app/user/user.service.ts b/overlord/src/app/user/user.service.ts index b7b6bc78..2ff72202 100644 --- a/overlord/src/app/user/user.service.ts +++ b/overlord/src/app/user/user.service.ts @@ -18,7 +18,7 @@ const serviceName = 'UserService'; export class UserService { constructor(private http: HttpClient, private log: ErrorLoggerService) {} - get(id: string): Observable { + get(id: string | null): Observable { const getUrl: string = id === null ? `${url}` : `${url}/${id}`; return >( this.http