feat: add Welcome Kit dispatch dialog and Share Holders view
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<mat-card class="container">
|
||||
<div class="table-header">
|
||||
<div class="table-header-row">
|
||||
<div class="table-title-group">
|
||||
<h2 class="table-title">
|
||||
<div class="t-logomark"><span></span><span></span><span></span><span></span></div>
|
||||
Share Holders
|
||||
</h2>
|
||||
<span class="table-count" *ngIf="totalRows">{{ totalRows }} records</span>
|
||||
</div>
|
||||
|
||||
<div class="table-buttons"></div>
|
||||
</div>
|
||||
|
||||
<div class="table-filters">
|
||||
<mat-form-field class="table-search" appearance="outline">
|
||||
<input matInput placeholder="Search by client name or account number" [formControl]="searchControl" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="table-filter" appearance="outline">
|
||||
<mat-select [(value)]="branchFilter" (selectionChange)="applyFilters()" placeholder="Branch">
|
||||
<mat-option value="">All Branches</mat-option>
|
||||
<mat-option *ngFor="let office of offices" [value]="office.id">
|
||||
{{ office.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="table-filter" appearance="outline">
|
||||
<mat-select [(value)]="productFilter" (selectionChange)="applyFilters()" placeholder="Share Product">
|
||||
<mat-option value="">All Products</mat-option>
|
||||
<mat-option *ngFor="let product of products" [value]="product.id">
|
||||
{{ product.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="table-filter" appearance="outline">
|
||||
<mat-select [(value)]="statusFilter" (selectionChange)="applyFilters()" placeholder="Status">
|
||||
<mat-option value="">All Status</mat-option>
|
||||
<mat-option value="active">Active</mat-option>
|
||||
<mat-option value="approved">Approved</mat-option>
|
||||
<mat-option value="submitted and pending approval">Pending</mat-option>
|
||||
<mat-option value="closed">Closed</mat-option>
|
||||
<mat-option value="rejected">Rejected</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="table-filter" appearance="outline" (click)="picker.open()">
|
||||
<mat-date-range-input [formGroup]="dateRangeForm" [rangePicker]="picker">
|
||||
<input matStartDate formControlName="start" placeholder="Start date" (dateChange)="applyFilters()" />
|
||||
<input matEndDate formControlName="end" placeholder="End date" (dateChange)="applyFilters()" />
|
||||
</mat-date-range-input>
|
||||
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-date-range-picker #picker></mat-date-range-picker>
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-icon-button matTooltip="Clear Filters" (click)="clearFilters()">
|
||||
<mat-icon>clear</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<mat-progress-bar *ngIf="isLoading" mode="indeterminate" class="table-loader"></mat-progress-bar>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
|
||||
<!-- Member Name Column (clientName) -->
|
||||
<ng-container matColumnDef="clientName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Client Name</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.clientName || 'N/A' }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Folio Number Column (accountNo) -->
|
||||
<ng-container matColumnDef="accountNo">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Account No</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.accountNo || 'N/A' }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Class Column (productName) -->
|
||||
<ng-container matColumnDef="productName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Share Product Name</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.productName || 'N/A' }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Approved Shares Column -->
|
||||
<ng-container matColumnDef="totalApprovedShares">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Approved Shares</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.totalApprovedShares || 0 }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Pending Shares Column -->
|
||||
<ng-container matColumnDef="totalPendingForApprovalShares">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Pending Shares</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.totalPendingForApprovalShares || 0 }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Currency Column -->
|
||||
<ng-container matColumnDef="currency">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Currency</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.currency?.code || 'N/A' }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Activated Date Column -->
|
||||
<ng-container matColumnDef="activatedDate">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Activated On</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{ element.activatedDate ? (element.activatedDate | dateFormat) : 'Not Activated' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<!-- We can route to /shares/{accountId} or /clients/{clientId}/shares-accounts/{accountId}/general depending on preference -->
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: displayedColumns"
|
||||
[routerLink]="['/clients', row.clientId, 'shares-accounts', row.id, 'general']"
|
||||
></tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
|
||||
</div>
|
||||
</mat-card>
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
.container {
|
||||
margin: 1.5rem;
|
||||
padding: 0;
|
||||
border-radius: var(--radius-xl);
|
||||
background: var(--bg-card);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.table-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
|
||||
.table-header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.table-title-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
.table-title {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-main);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
letter-spacing: -0.01em;
|
||||
|
||||
.t-logomark {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
padding: 2px;
|
||||
background: var(--bg-hover);
|
||||
border-radius: 6px;
|
||||
|
||||
span {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 2px;
|
||||
background: var(--primary-main);
|
||||
opacity: 0.8;
|
||||
|
||||
&:nth-child(2) {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-count {
|
||||
padding: 0.25rem 0.75rem;
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-muted);
|
||||
border-radius: var(--radius-full);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
}
|
||||
|
||||
.table-filters {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
mat-form-field {
|
||||
width: 240px;
|
||||
margin-bottom: -1.25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-container {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
border-radius: 0 0 var(--radius-xl) var(--radius-xl);
|
||||
|
||||
.table-loader {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.modern-table {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
|
||||
tr.mat-row {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Component, OnInit, ViewChild, inject } from '@angular/core';
|
||||
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatSort, MatSortModule } from '@angular/material/sort';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatNativeDateModule } from '@angular/material/core';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { FormsModule, ReactiveFormsModule, FormGroup, FormControl } from '@angular/forms';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { forkJoin, of } from 'rxjs';
|
||||
import { catchError, debounceTime, distinctUntilChanged, filter, switchMap, tap, startWith } from 'rxjs/operators';
|
||||
import { DateFormatPipe } from '../../pipes/date-format.pipe';
|
||||
import { SharesService } from '../shares.service';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-share-holders',
|
||||
templateUrl: './share-holders.component.html',
|
||||
styleUrls: ['./share-holders.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule,
|
||||
MatCardModule,
|
||||
MatTableModule,
|
||||
MatPaginatorModule,
|
||||
MatSortModule,
|
||||
MatProgressBarModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatDatepickerModule,
|
||||
MatNativeDateModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
MatTooltipModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
DateFormatPipe
|
||||
]
|
||||
})
|
||||
export class ShareHoldersComponent implements OnInit {
|
||||
displayedColumns: string[] = [
|
||||
'clientName',
|
||||
'accountNo',
|
||||
'productName',
|
||||
'totalApprovedShares',
|
||||
'totalPendingForApprovalShares',
|
||||
'currency',
|
||||
'activatedDate'
|
||||
];
|
||||
dataSource = new MatTableDataSource<any>();
|
||||
isLoading = true;
|
||||
totalRows = 0;
|
||||
|
||||
// Filter Data
|
||||
offices: any[] = [];
|
||||
products: any[] = [];
|
||||
|
||||
// Filter State
|
||||
searchValue = '';
|
||||
branchFilter = '';
|
||||
productFilter = '';
|
||||
statusFilter = '';
|
||||
dateRangeForm = new FormGroup({
|
||||
start: new FormControl<Date | null>(null),
|
||||
end: new FormControl<Date | null>(null)
|
||||
});
|
||||
|
||||
@ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
|
||||
@ViewChild(MatSort, { static: true }) sort!: MatSort;
|
||||
|
||||
searchControl = new FormControl('');
|
||||
|
||||
private sharesService = inject(SharesService);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
this.setupFilterPredicate();
|
||||
|
||||
this.searchControl.valueChanges
|
||||
.pipe(
|
||||
startWith(''),
|
||||
debounceTime(400),
|
||||
distinctUntilChanged(),
|
||||
filter((term: string | null) => !term || term.trim().length === 0 || term.trim().length >= 2),
|
||||
tap((term: string | null) => {
|
||||
this.searchValue = term || '';
|
||||
this.isLoading = true;
|
||||
}),
|
||||
switchMap((term: string | null) => {
|
||||
const search = term ? term.trim() : '';
|
||||
return forkJoin({
|
||||
accounts: this.sharesService.getShareAccountSummaries(search || undefined).pipe(catchError(() => of([]))),
|
||||
offices: this.sharesService.getOffices().pipe(catchError(() => of([]))),
|
||||
products: this.sharesService.getShareProducts().pipe(catchError(() => of([])))
|
||||
});
|
||||
})
|
||||
)
|
||||
.subscribe({
|
||||
next: (results: any) => {
|
||||
this.offices = results.offices || [];
|
||||
|
||||
const allShareAccounts = results.accounts || [];
|
||||
|
||||
// Derive unique products from the actual active share accounts to ensure no garbage/dummy
|
||||
// products (like NextGen Tech Pvt Ltd) show up in the dropdown.
|
||||
const uniqueProductsMap = new Map();
|
||||
allShareAccounts.forEach((acc: any) => {
|
||||
if (acc.productId && acc.productName) {
|
||||
uniqueProductsMap.set(acc.productId, { id: acc.productId, name: acc.productName });
|
||||
}
|
||||
});
|
||||
this.products = Array.from(uniqueProductsMap.values());
|
||||
|
||||
this.dataSource.data = allShareAccounts;
|
||||
this.totalRows = allShareAccounts.length;
|
||||
this.isLoading = false;
|
||||
},
|
||||
error: () => {
|
||||
this.isLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupFilterPredicate() {
|
||||
this.dataSource.filterPredicate = (data: any, filter: string) => {
|
||||
let branchMatch = true;
|
||||
if (this.branchFilter) {
|
||||
branchMatch = data.officeId?.toString() === this.branchFilter.toString();
|
||||
}
|
||||
|
||||
let productMatch = true;
|
||||
if (this.productFilter) {
|
||||
productMatch = data.productId?.toString() === this.productFilter.toString();
|
||||
}
|
||||
|
||||
let statusMatch = true;
|
||||
if (this.statusFilter) {
|
||||
statusMatch =
|
||||
data.status?.value?.toLowerCase() === this.statusFilter.toLowerCase() ||
|
||||
data.status?.id?.toString() === this.statusFilter.toString();
|
||||
}
|
||||
|
||||
let dateMatch = true;
|
||||
if (this.dateRangeForm.value.start && data.activatedDate) {
|
||||
const rowDate = new Date(data.activatedDate[0], data.activatedDate[1] - 1, data.activatedDate[2]);
|
||||
const start = this.dateRangeForm.value.start;
|
||||
const end = this.dateRangeForm.value.end || new Date(); // If no end date, use today
|
||||
|
||||
// Reset hours for accurate comparison
|
||||
rowDate.setHours(0, 0, 0, 0);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end.setHours(0, 0, 0, 0);
|
||||
|
||||
dateMatch = rowDate >= start && rowDate <= end;
|
||||
}
|
||||
|
||||
return branchMatch && productMatch && statusMatch && dateMatch;
|
||||
};
|
||||
}
|
||||
|
||||
applyFilters() {
|
||||
// We update the filter string to a random/unique value to trigger the filterPredicate
|
||||
this.dataSource.filter = Math.random().toString();
|
||||
}
|
||||
|
||||
search(filterValue: string) {
|
||||
this.searchControl.setValue(filterValue);
|
||||
}
|
||||
|
||||
clearFilters() {
|
||||
this.searchValue = '';
|
||||
this.branchFilter = '';
|
||||
this.productFilter = '';
|
||||
this.statusFilter = '';
|
||||
this.dateRangeForm.reset();
|
||||
|
||||
// Trigger server-side reload without search term
|
||||
this.searchControl.setValue('', { emitEvent: true });
|
||||
// Trigger client-side filters update
|
||||
this.applyFilters();
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import { DividendsTabComponent } from './shares-account-view/dividends-tab/divid
|
||||
import { CreateSharesAccountComponent } from './create-shares-account/create-shares-account.component';
|
||||
import { EditSharesAccountComponent } from './edit-shares-account/edit-shares-account.component';
|
||||
import { SharesAccountActionsComponent } from './shares-account-actions/shares-account-actions.component';
|
||||
import { EditShareAccountDetailsComponent } from './edit-share-account-details/edit-share-account-details.component';
|
||||
import { ShareHoldersComponent } from './share-holders/share-holders.component';
|
||||
|
||||
/** Custom Resolvers */
|
||||
import { SharesAccountViewResolver } from './common-resolvers/share-account-view.resolver';
|
||||
@@ -33,6 +33,11 @@ const routes: Routes = [
|
||||
path: '',
|
||||
data: { title: 'Shares', breadcrumb: 'Shares', routeParamBreadcrumb: false, addBreadcrumbLink: false },
|
||||
children: [
|
||||
{
|
||||
path: 'share-holders',
|
||||
data: { title: 'Share Holders', breadcrumb: 'Share Holders' },
|
||||
component: ShareHoldersComponent
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
data: { title: 'Create Shares Account', breadcrumb: 'Create Shares Account' },
|
||||
@@ -90,14 +95,6 @@ const routes: Routes = [
|
||||
sharesAccountAndTemplate: SharesAccountAndTemplateResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'edit-details',
|
||||
data: { title: 'Edit Share Account Details', breadcrumb: 'Edit Details', routeParamBreadcrumb: false },
|
||||
component: EditShareAccountDetailsComponent,
|
||||
resolve: {
|
||||
sharesAccountAndTemplate: SharesAccountAndTemplateResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'actions/:name',
|
||||
data: { title: 'Shares Account Actions', breadcrumb: 'Actions', routeParamBreadcrumb: 'name' },
|
||||
|
||||
@@ -37,6 +37,15 @@ import { ApproveShareDialogComponent } from './shares-account-actions/approve-sh
|
||||
import { RejectSharesComponent } from './shares-account-actions/reject-shares/reject-shares.component';
|
||||
import { RejectShareDialogComponent } from './shares-account-actions/reject-shares/reject-share-dialog/reject-share-dialog.component';
|
||||
import { GeneralTabComponent } from './shares-account-view/general-tab/general-tab.component';
|
||||
import { ShareHoldersComponent } from './share-holders/share-holders.component';
|
||||
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
|
||||
/**
|
||||
* Shares Module
|
||||
@@ -45,6 +54,15 @@ import { GeneralTabComponent } from './shares-account-view/general-tab/general-t
|
||||
*/
|
||||
@NgModule({
|
||||
imports: [
|
||||
ShareHoldersComponent,
|
||||
SharedModule,
|
||||
MatTableModule,
|
||||
MatPaginatorModule,
|
||||
MatSortModule,
|
||||
MatCardModule,
|
||||
MatProgressBarModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
SharedModule,
|
||||
PipesModule,
|
||||
DirectivesModule,
|
||||
|
||||
@@ -22,6 +22,39 @@ import { Observable } from 'rxjs';
|
||||
export class SharesService {
|
||||
private http = inject(HttpClient);
|
||||
|
||||
/**
|
||||
* @param {any} params Optional HTTP params (like offset, limit, search)
|
||||
* @returns {Observable<any>} List of all share accounts
|
||||
*/
|
||||
getSharesAccounts(params?: any): Observable<any> {
|
||||
return this.http.get('/accounts/share', { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<any>} List of all share accounts with client details summary
|
||||
*/
|
||||
getShareAccountSummaries(searchTerm?: string): Observable<any> {
|
||||
let params = new HttpParams();
|
||||
if (searchTerm) {
|
||||
params = params.set('searchTerm', searchTerm);
|
||||
}
|
||||
return this.http.get('/shareaccounts/summary', { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<any>} List of all offices
|
||||
*/
|
||||
getOffices(): Observable<any> {
|
||||
return this.http.get('/offices');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<any>} List of all share products
|
||||
*/
|
||||
getShareProducts(): Observable<any> {
|
||||
return this.http.get('/products/share');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} accountId Shares Account Id of account to get data for.
|
||||
* @param {boolean} template Shares account template required?.
|
||||
|
||||
Reference in New Issue
Block a user