feat(shares): real-profit driven dividend UI, Dividend Dashboard, and member claim workflow

Frontend counterpart to the backend dividend rework:
- Create Dividend screen auto-computes the amount from real bank profit x officer
  percentage, blocks overlapping declaration periods, and adds a Total Dividend Paid
  summary card.
- View Dividend page modernized and reusable as an embedded popup from the new
  cross-product "Dividend Requests" approval-center tab.
- New consolidated Dividend Dashboard: summary stats, every declaration and its
  status, locked-dividend-vs-overdue-EMI matching with a proactive apply-to-loan
  action, full 3-year lock history, and a maker-checker claim panel letting a clerk
  split a member's unclaimed dividend across an overdue loan, savings credit, or
  cash for a manager to approve.
- Menu/routing consolidated under Share > Dividend Management; removed the
  now-unreachable Failed Dividend Payouts page and its home-dashboard alert once the
  backend stopped needing a retry path for stuck payouts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 12:43:18 +05:30
co-authored by Claude Sonnet 5
parent 01e86173c1
commit c34f6a0131
24 changed files with 2004 additions and 39 deletions
@@ -51,9 +51,43 @@
}
</mat-form-field>
@if (overlapError) {
<p class="dividend-profit-note dividend-profit-note--error">{{ overlapError }}</p>
}
@if (loadingProfit) {
<p class="dividend-profit-note">
{{ 'labels.inputs.Fetching bank profit for this period' | translate }}...
</p>
} @else if (profitLookupError) {
<p class="dividend-profit-note dividend-profit-note--error">
{{ 'labels.inputs.Could not fetch bank profit for this period' | translate }}.
</p>
} @else if (netProfit !== null) {
<p class="dividend-profit-note">
{{ 'labels.inputs.Bank Profit for this Period' | translate }}:
<strong>{{ netProfit | number: '1.2-2' }}</strong>
</p>
}
<mat-form-field>
<mat-label>{{ 'labels.inputs.Dividend Amount' | translate }}</mat-label>
<input matInput required formControlName="dividendAmount" />
<mat-label>{{ 'labels.inputs.Dividend Percentage' | translate }} (%)</mat-label>
<input matInput type="number" min="0" max="100" step="0.01" required formControlName="dividendPercentage" />
@if (createDividendForm.controls.dividendPercentage.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Dividend Percentage' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field>
<mat-label
>{{ 'labels.inputs.Dividend Amount' | translate }} ({{
'labels.inputs.auto-computed' | translate
}})</mat-label
>
<input matInput required readonly formControlName="dividendAmount" />
@if (createDividendForm.controls.dividendAmount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.is' | translate }}
@@ -68,7 +102,7 @@
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" [disabled]="!createDividendForm.valid">
<button mat-raised-button color="primary" [disabled]="!createDividendForm.valid || !!overlapError">
{{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
@@ -9,3 +9,17 @@
.container {
max-width: 37rem;
}
.dividend-profit-note {
margin: 0 0 12px;
padding: 8px 12px;
border-radius: 6px;
background: #eafaf0;
color: #1a7f37;
font-size: 13px;
&--error {
background: #fdecea;
color: #b42318;
}
}
@@ -15,6 +15,7 @@ import { Dates } from 'app/core/utils/dates';
/** Custom Services. */
import { ProductsService } from 'app/products/products.service';
import { SettingsService } from 'app/settings/settings.service';
import { AccountingService } from 'app/accounting/accounting.service';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -33,6 +34,7 @@ export class CreateDividendComponent implements OnInit {
private route = inject(ActivatedRoute);
private dateUtils = inject(Dates);
private productService = inject(ProductsService);
private accountingService = inject(AccountingService);
private router = inject(Router);
private settingsService = inject(SettingsService);
@@ -45,6 +47,18 @@ export class CreateDividendComponent implements OnInit {
/** Maximum Date allowed. */
maxDate = new Date();
/** Real bank profit for the selected period (fetched from posted GL journal entries). */
netProfit: number | null = null;
/** True while the profit figure for the selected period is being fetched. */
loadingProfit = false;
/** Set if the profit lookup fails, so the officer knows the amount below isn't auto-computed. */
profitLookupError = false;
/** Every dividend already declared for this product, used to block overlapping periods. */
private existingDividendPeriods: { start: Date; end: Date }[] = [];
/** Set when the currently selected period overlaps an existing declaration for this product. */
overlapError: string | null = null;
/**
* Get Share Product data from `Resolver`.
* @param {FormBuilder} formBuilder Form Builder.
@@ -63,6 +77,64 @@ export class CreateDividendComponent implements OnInit {
ngOnInit() {
this.maxDate = this.settingsService.businessDate;
this.setDividendForm();
this.loadExistingDividendPeriods();
const onPeriodChange = () => {
this.fetchProfitForSelectedPeriod();
this.checkOverlap();
};
this.createDividendForm.get('dividendPeriodStartDate').valueChanges.subscribe(onPeriodChange);
this.createDividendForm.get('dividendPeriodEndDate').valueChanges.subscribe(onPeriodChange);
this.createDividendForm.get('dividendPercentage').valueChanges.subscribe(() => this.recomputeDividendAmount());
}
/** Parses a Fineract date field, which may arrive as `[yyyy, MM, dd]` or an ISO string. */
private parseFineractDate(value: any): Date | null {
if (!value) {
return null;
}
if (Array.isArray(value)) {
const [
year,
month,
day
] = value;
return new Date(year, month - 1, day);
}
const parsed = new Date(value);
return isNaN(parsed.getTime()) ? null : parsed;
}
/**
* Loads every dividend already declared for this product (any status -- this product has no
* "rejected" state, see backend `ShareProductDividendStatusType`) so a new period that overlaps
* one of them can be flagged before the officer even submits.
*/
private loadExistingDividendPeriods(): void {
this.productService.getDividends(this.shareProductData.id).subscribe((response: any) => {
const items = response?.pageItems || response || [];
this.existingDividendPeriods = items
.map((item: any) => ({
start: this.parseFineractDate(item.dividendPeriodStartDate),
end: this.parseFineractDate(item.dividendPeriodEndDate)
}))
.filter((period: any) => period.start && period.end);
this.checkOverlap();
});
}
/** Blocks a new period that overlaps any existing declaration for this product. */
checkOverlap(): void {
const start: Date = this.createDividendForm.get('dividendPeriodStartDate').value;
const end: Date = this.createDividendForm.get('dividendPeriodEndDate').value;
if (!(start instanceof Date) || !(end instanceof Date)) {
this.overlapError = null;
return;
}
const overlapping = this.existingDividendPeriods.find((period) => period.start <= end && period.end >= start);
this.overlapError = overlapping
? `This product already has a dividend declared from ${overlapping.start.toLocaleDateString()} to ${overlapping.end.toLocaleDateString()}. Choose a period that doesn't overlap.`
: null;
}
/**
@@ -78,6 +150,14 @@ export class CreateDividendComponent implements OnInit {
'',
Validators.required
],
dividendPercentage: [
'',
[
Validators.required,
Validators.min(0),
Validators.max(100)
]
],
dividendAmount: [
'',
Validators.required
@@ -85,10 +165,56 @@ export class CreateDividendComponent implements OnInit {
});
}
/** yyyy-MM-dd, independent of the tenant's display date format — this is what the backend expects. */
private toIsoDate(date: Date): string {
const pad = (n: number) => String(n).padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
}
/**
* Looks up the bank's real net profit for the selected dividend period, once both dates are
* set, so the officer can base the dividend on actual profit instead of guessing an amount.
*/
fetchProfitForSelectedPeriod(): void {
const start: Date = this.createDividendForm.get('dividendPeriodStartDate').value;
const end: Date = this.createDividendForm.get('dividendPeriodEndDate').value;
if (!(start instanceof Date) || !(end instanceof Date)) {
return;
}
this.loadingProfit = true;
this.profitLookupError = false;
this.accountingService.getProfitAndLoss(this.toIsoDate(start), this.toIsoDate(end)).subscribe({
next: (response: any) => {
this.netProfit = Number(response.netProfit) || 0;
this.loadingProfit = false;
this.recomputeDividendAmount();
},
error: () => {
this.netProfit = null;
this.loadingProfit = false;
this.profitLookupError = true;
}
});
}
/** Dividend Amount = Net Profit x Dividend Percentage / 100 — no manual entry needed. */
recomputeDividendAmount(): void {
const percentage = Number(this.createDividendForm.get('dividendPercentage').value);
if (this.netProfit === null || !percentage || percentage <= 0) {
this.createDividendForm.get('dividendAmount').setValue('');
return;
}
const amount = (this.netProfit * percentage) / 100;
this.createDividendForm.get('dividendAmount').setValue(Math.round(amount * 100) / 100);
}
/**
* Submits Create Dividend form.
*/
submit() {
if (this.overlapError) {
return;
}
const createDividendFormData = this.createDividendForm.value;
const locale = this.settingsService.language.code;
const dateFormat = this.settingsService.dateFormat;
@@ -105,6 +231,9 @@ export class CreateDividendComponent implements OnInit {
dateFormat,
locale
};
// dividendPercentage only drives the auto-computed dividendAmount above — Fineract's
// create-dividend command doesn't recognise it.
delete data.dividendPercentage;
this.productService.createDividend(this.shareProductData.id, data).subscribe((response: any) => {
this.router.navigate(['../'], { relativeTo: this.route });
});
@@ -14,6 +14,34 @@
</div>
<div class="container">
<div class="dpd-summary-card">
<div class="dpd-summary-card__header">{{ 'labels.inputs.Total Dividend Paid' | translate }}</div>
<div class="layout-row gap-10px align-center" style="flex-wrap: wrap">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.From Date' | translate }}</mat-label>
<input matInput type="date" [(ngModel)]="dividendPaidFromDate" (change)="loadDividendPaidReport()" />
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.To Date' | translate }}</mat-label>
<input matInput type="date" [(ngModel)]="dividendPaidToDate" (change)="loadDividendPaidReport()" />
</mat-form-field>
@if (loadingDividendPaidReport) {
<span class="dpd-summary-card__value">{{ 'labels.inputs.Loading' | translate }}…</span>
} @else if (dividendPaidReport) {
<span class="dpd-summary-card__value">{{ dividendPaidReport.totalPaid | number: '1.2-2' }}</span>
<span class="dpd-summary-card__meta">
({{ dividendPaidReport.payoutCount }} {{ 'labels.inputs.payouts' | translate }})
</span>
@if (dividendPaidReport.glAccountConfigured === false) {
<span class="dpd-summary-card__warn">
{{ 'labels.inputs.Dividend Payable GL account not mapped yet' | translate }} (Accounting → Financial
Activity Mappings)
</span>
}
}
</div>
</div>
<div class="layout-row">
<mat-form-field appearance="outline" class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
@@ -13,3 +13,41 @@ table {
cursor: pointer;
}
}
.dpd-summary-card {
background: #eafaf0;
border: 1px solid rgb(26 127 55 / 25%);
border-radius: 10px;
padding: 14px 18px;
margin-bottom: 16px;
&__header {
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.02em;
color: #1a7f37;
margin-bottom: 8px;
}
&__value {
font-size: 22px;
font-weight: 700;
color: #1a7f37;
}
&__meta {
font-size: 13px;
color: #475569;
}
&__warn {
font-size: 12.5px;
color: #b42318;
font-weight: 600;
}
mat-form-field {
width: 160px;
}
}
@@ -31,6 +31,9 @@ import { MatFormField, MatLabel } from '@angular/material/form-field';
import { MatInput } from '@angular/material/input';
import { TranslatePipe as NgxTranslatePipe } from '@ngx-translate/core';
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AccountingService } from '../../../accounting/accounting.service';
/**
* Dividends component.
@@ -40,6 +43,8 @@ import { DateFormatPipe } from '../../../pipes/date-format.pipe';
templateUrl: './dividends.component.html',
styleUrls: ['./dividends.component.scss'],
imports: [
CommonModule,
FormsModule,
HasPermissionDirective,
MatButton,
RouterLink,
@@ -67,6 +72,13 @@ import { DateFormatPipe } from '../../../pipes/date-format.pipe';
export class ShareProductsDividendsComponent implements OnInit {
private route = inject(ActivatedRoute);
private router = inject(Router);
private accountingService = inject(AccountingService);
/** "Total Dividend Paid" report state — derived from the Dividend Payable GL ledger. */
dividendPaidFromDate: string;
dividendPaidToDate: string;
dividendPaidReport: any = null;
loadingDividendPaidReport = false;
/** Dividends data. */
dividendData: any;
@@ -101,6 +113,39 @@ export class ShareProductsDividendsComponent implements OnInit {
*/
ngOnInit() {
this.setDividends();
const today = new Date();
const threeYearsAgo = new Date(today);
threeYearsAgo.setFullYear(threeYearsAgo.getFullYear() - 3);
this.dividendPaidToDate = this.toIsoDate(today);
this.dividendPaidFromDate = this.toIsoDate(threeYearsAgo);
this.loadDividendPaidReport();
}
private toIsoDate(date: Date): string {
const pad = (n: number) => String(n).padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
}
/**
* "Total Dividend Paid" for the selected period — a report over the existing Dividend Payable
* GL account's ledger, not a separate account (real-world CBS practice).
*/
loadDividendPaidReport() {
if (!this.dividendPaidFromDate || !this.dividendPaidToDate) {
return;
}
this.loadingDividendPaidReport = true;
this.accountingService.getDividendPaidReport(this.dividendPaidFromDate, this.dividendPaidToDate).subscribe({
next: (response: any) => {
this.dividendPaidReport = response;
this.loadingDividendPaidReport = false;
},
error: () => {
this.dividendPaidReport = null;
this.loadingDividendPaidReport = false;
}
});
}
/**
@@ -6,26 +6,69 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
@if (!isdividendPosted) {
<div class="container m-b-20 layout-row layout-align-end gap-20px">
<button mat-raised-button color="primary" *mifosxHasPermission="'POST_DIVIDENDS'" (click)="postDividends()">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.buttons' | translate }}
</button>
</div>
}
<div class="vd-container">
<div class="pg-card">
<div class="vd-header">
<div class="vd-header__title">
<fa-icon icon="hand-holding-usd" class="vd-header__icon"></fa-icon>
<span
>{{ 'labels.inputs.Dividend Approval' | translate }} —
{{ 'labels.inputs.Per-Member Breakdown' | translate }}</span
>
</div>
<div class="vd-header__actions">
@if (!isdividendPosted) {
<button
mat-raised-button
color="primary"
class="vd-approve-btn"
*mifosxHasPermission="'POST_DIVIDENDS'"
(click)="postDividends()"
>
<fa-icon icon="check" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Approve' | translate }}
</button>
}
@if (embedded) {
<button type="button" class="vd-close-btn" (click)="close()" aria-label="Close">
<fa-icon icon="times"></fa-icon>
</button>
}
</div>
</div>
<div class="container">
<div class="layout-row">
<mat-form-field appearance="outline" class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<i matPrefix class="fas fa-search"></i>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
</div>
<div class="vd-summary">
<div class="vd-summary__stat">
<span class="vd-summary__label">{{ 'labels.inputs.Total Amount' | translate }}</span>
<span class="vd-summary__value">{{ totalAmount | number: '1.2-2' }}</span>
</div>
<div class="vd-summary__stat">
<span class="vd-summary__label">{{ 'labels.inputs.Members' | translate }}</span>
<span class="vd-summary__value">{{ dividendData.pageItems.length }}</span>
</div>
<div class="vd-summary__stat">
<span class="vd-summary__label">{{ 'labels.inputs.Status' | translate }}</span>
<span [class]="isdividendPosted ? 'vd-badge vd-badge--posted' : 'vd-badge vd-badge--pending'">
{{
isdividendPosted ? ('labels.inputs.Approved' | translate) : ('labels.inputs.Awaiting Approval' | translate)
}}
</span>
</div>
</div>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<div class="vd-filter">
<div class="vd-search">
<fa-icon icon="search" class="vd-search__icon"></fa-icon>
<input
class="vd-search__input"
type="text"
placeholder="{{ 'labels.inputs.Filter' | translate }}"
(keyup)="applyFilter($event.target.value)"
/>
</div>
</div>
<table mat-table [dataSource]="dataSource" matSort class="pg-table">
<ng-container matColumnDef="clientName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Client Name' | translate }}</th>
<td mat-cell *matCellDef="let dividend">{{ dividend.accountData.clientName }}</td>
@@ -33,17 +76,23 @@
<ng-container matColumnDef="shareAccount">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Share Account' | translate }}</th>
<td mat-cell *matCellDef="let dividend">{{ dividend.accountData.accountNo }}</td>
<td mat-cell *matCellDef="let dividend">
<span class="pg-chip">{{ dividend.accountData.accountNo }}</span>
</td>
</ng-container>
<ng-container matColumnDef="dividendAmount">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Dividend Amount' | translate }}</th>
<td mat-cell *matCellDef="let dividend">{{ dividend.amount }}</td>
<td mat-cell *matCellDef="let dividend" class="num" [class.vd-amount--zero]="dividend.amount === 0">
{{ dividend.amount | number: '1.2-2' }}
</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Status' | translate }}</th>
<td mat-cell *matCellDef="let dividend">{{ dividend.status.value }}</td>
<td mat-cell *matCellDef="let dividend">
<span [class]="statusChipClass(dividend.status.value)">{{ dividend.status.value }}</span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
@@ -6,6 +6,222 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
$primary: #2563eb;
$surface: #fff;
$surface-alt: #f8fafc;
$border: #e2e8f0;
$text-primary: #1e293b;
$text-secondary: #64748b;
.vd-container {
max-width: 100%;
}
.pg-card {
background: $surface;
border: 1px solid $border;
border-radius: 12px;
box-shadow:
0 1px 3px rgb(0 0 0 / 7%),
0 1px 2px rgb(0 0 0 / 4%);
padding: 20px 24px;
}
.vd-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 18px;
&__title {
display: flex;
align-items: center;
gap: 10px;
font-size: 15px;
font-weight: 700;
color: $text-primary;
}
&__icon {
color: $primary;
font-size: 16px;
}
}
.vd-header__actions {
display: flex;
align-items: center;
gap: 4px;
}
.vd-approve-btn {
font-weight: 600;
}
.vd-close-btn {
width: 34px;
height: 34px;
border-radius: 50%;
border: 1px solid $border;
background: $surface;
color: $text-secondary;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 13px;
padding: 0;
transition:
background 0.15s ease,
border-color 0.15s ease,
color 0.15s ease;
&:hover {
background: #fdecea;
border-color: #f3a79a;
color: #b42318;
}
&:focus-visible {
outline: 2px solid $primary;
outline-offset: 2px;
}
}
.vd-summary {
display: flex;
gap: 28px;
flex-wrap: wrap;
background: $surface-alt;
border: 1px solid $border;
border-radius: 10px;
padding: 14px 20px;
margin-bottom: 18px;
&__stat {
display: flex;
flex-direction: column;
gap: 4px;
}
&__label {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
color: $text-secondary;
}
&__value {
font-size: 18px;
font-weight: 700;
color: $text-primary;
}
}
.vd-filter {
margin-bottom: 16px;
}
.vd-search {
display: flex;
align-items: center;
gap: 10px;
max-width: 320px;
background: $surface;
border: 1px solid $border;
border-radius: 999px;
padding: 9px 16px;
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
&:focus-within {
border-color: $primary;
box-shadow: 0 0 0 3px rgb(37 99 235 / 12%);
}
&__icon {
color: $text-secondary;
font-size: 12px;
flex-shrink: 0;
}
&__input {
border: none;
outline: none;
background: transparent;
font-size: 13.5px;
color: $text-primary;
flex: 1;
min-width: 0;
&::placeholder {
color: #94a3b8;
}
}
}
.vd-badge {
display: inline-flex;
align-items: center;
width: fit-content;
padding: 4px 12px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
&--pending {
background: #fffbeb;
color: #92400e;
}
&--approved {
background: #eff6ff;
color: #1d4ed8;
}
&--posted {
background: #eafaf0;
color: #1a7f37;
}
}
.pg-chip {
display: inline-flex;
align-items: center;
padding: 3px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
background: #f1f5f9;
color: $text-primary;
}
.vd-amount--zero {
color: $text-secondary;
}
table {
width: 100%;
}
.pg-table {
th {
font-size: 11.5px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.02em;
color: $text-secondary;
}
td {
font-size: 13.5px;
}
.num {
text-align: right;
}
}
@@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { Component, OnInit, ViewChild, inject } from '@angular/core';
import { Component, OnInit, Input, Output, EventEmitter, ViewChild, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort, MatSortHeader } from '@angular/material/sort';
@@ -54,9 +54,18 @@ export class ViewDividendComponent implements OnInit {
private productsService = inject(ProductsService);
private router = inject(Router);
/** Set to render standalone (e.g. inside a popup from the Dividend Requests tab) instead of
* resolving productId/dividendId/data from the Angular route. */
@Input() embedded = false;
@Input() overrideProductId: string | number | null = null;
@Input() overrideDividendId: string | number | null = null;
@Output() closeRequested = new EventEmitter<void>();
@Output() actionCompleted = new EventEmitter<void>();
dividendData: any;
status: any;
isdividendPosted = false;
totalAmount = 0;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
@ViewChild(MatSort, { static: true }) sort: MatSort;
@@ -68,32 +77,61 @@ export class ViewDividendComponent implements OnInit {
];
dataSource: MatTableDataSource<any>;
constructor() {
this.route.data.subscribe((data: { dividendData: any }) => {
this.dividendData = data.dividendData;
});
this.status = this.route.snapshot.queryParams['status'];
if (this.status && (this.status === 'Dividend Approved' || this.status === 'Dividend Posted')) {
this.isdividendPosted = true;
ngOnInit() {
if (this.embedded) {
this.productsService
.getDividendData(this.overrideProductId, this.overrideDividendId)
.subscribe((dividendData: any) => this.setDividendData(dividendData));
} else {
this.status = this.route.snapshot.queryParams['status'];
if (this.status && (this.status === 'Dividend Approved' || this.status === 'Dividend Posted')) {
this.isdividendPosted = true;
}
this.route.data.subscribe((data: { dividendData: any }) => this.setDividendData(data.dividendData));
}
}
ngOnInit() {
this.dataSource = new MatTableDataSource(this.dividendData.pageItems);
private setDividendData(dividendData: any) {
this.dividendData = dividendData;
this.dataSource = new MatTableDataSource(dividendData.pageItems);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
this.totalAmount = (dividendData.pageItems ?? []).reduce(
(sum: number, item: any) => sum + Number(item.amount ?? 0),
0
);
}
statusChipClass(status: string): string {
if (status === 'Dividend Posted') {
return 'vd-badge vd-badge--posted';
}
if (status === 'Dividend Approved') {
return 'vd-badge vd-badge--approved';
}
return 'vd-badge vd-badge--pending';
}
postDividends() {
const shareProductId = this.route.parent.parent.snapshot.paramMap.get('productId');
const dividendId = this.route.snapshot.paramMap.get('dividendId');
const shareProductId = this.embedded
? this.overrideProductId
: this.route.parent.parent.snapshot.paramMap.get('productId');
const dividendId = this.embedded ? this.overrideDividendId : this.route.snapshot.paramMap.get('dividendId');
this.productsService
.approveDividend(shareProductId, dividendId, { productId: shareProductId, dividendId: dividendId })
.subscribe(() => {
this.router.navigate(['../'], { relativeTo: this.route });
if (this.embedded) {
this.actionCompleted.emit();
} else {
this.router.navigate(['../'], { relativeTo: this.route });
}
});
}
close(): void {
this.closeRequested.emit();
}
applyFilter(filterValue: string) {
this.dataSource.filter = filterValue.trim().toLowerCase();
}