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
@@ -0,0 +1,103 @@
/**
* 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, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { catchError } from 'rxjs/operators';
import { of } from 'rxjs';
import { PendingDividendsService } from './pending-dividends.service';
import { ProductsService } from '../../../products/products.service';
import { ViewDividendComponent } from '../../../products/share-products/view-dividend/view-dividend.component';
/**
* Approval Center tab — every Share Product dividend declaration still awaiting approval, across
* all products (Fineract's own screens only show one product's dividends at a time). Approving
* directly from here calls the same native Fineract "approve dividend" command used by the
* per-product View Dividend page.
*/
@Component({
selector: 'mifosx-dividend-requests',
templateUrl: './dividend-requests.component.html',
styleUrls: [
'../../../cashier/cashier-shared.scss',
'../../../clerk/clerk-shared.scss',
'./dividend-requests.component.scss'
],
standalone: true,
imports: [
CommonModule,
ViewDividendComponent
]
})
export class DividendRequestsComponent implements OnInit {
private service = inject(PendingDividendsService);
private productsService = inject(ProductsService);
rows: any[] = [];
loading = false;
loaded = false;
approvingId: number | null = null;
approveError: Record<number, string> = {};
showViewModal = false;
viewProductId: number | null = null;
viewDividendId: number | null = null;
ngOnInit(): void {
this.load();
}
openView(row: any): void {
this.viewProductId = row.productId;
this.viewDividendId = row.dividendId;
this.showViewModal = true;
}
closeViewModal(): void {
this.showViewModal = false;
this.viewProductId = null;
this.viewDividendId = null;
}
onViewActionCompleted(): void {
this.closeViewModal();
this.load();
}
load(): void {
this.loading = true;
this.service
.list()
.pipe(catchError(() => of([])))
.subscribe((res: any[]) => {
this.rows = res ?? [];
this.loading = false;
this.loaded = true;
});
}
approve(row: any): void {
this.approvingId = row.dividendId;
delete this.approveError[row.dividendId];
this.productsService
.approveDividend(row.productId, row.dividendId, { productId: row.productId, dividendId: row.dividendId })
.subscribe({
next: () => {
this.approvingId = null;
this.load();
},
error: (err: any) => {
this.approvingId = null;
this.approveError[row.dividendId] =
err?.error?.defaultUserMessage || err?.error?.message || 'Approve failed.';
}
});
}
}