diff --git a/src/app/core/shell/shell.component.ts b/src/app/core/shell/shell.component.ts
index e61129c1a..0ceee8562 100644
--- a/src/app/core/shell/shell.component.ts
+++ b/src/app/core/shell/shell.component.ts
@@ -98,6 +98,10 @@ export class ShellComponent implements OnInit, OnDestroy {
rbiMenuOpen = false;
rbiSearch = '';
+ /** Investment (Treasury) mega menu state */
+ investmentMenuOpen = false;
+ investmentSearch = '';
+
/* Live KPI ticker — markup is static inside the template; the marquee
animation, LIVE badge, and pause-on-hover handle the live feel. */
@@ -116,6 +120,11 @@ export class ShellComponent implements OnInit, OnDestroy {
return label.toLowerCase().includes(this.rbiSearch.toLowerCase());
}
+ matchesInvestmentSearch(label: string): boolean {
+ if (!this.investmentSearch) return true;
+ return label.toLowerCase().includes(this.investmentSearch.toLowerCase());
+ }
+
/** Route for a facility entry in the Account Facilities hover dropdown — only "Cheque Book" has a
* dedicated action page today; every other facility falls back to the existing overview page. */
facilityRoute(label: string): string[] {
@@ -312,7 +321,10 @@ export class ShellComponent implements OnInit, OnDestroy {
height: '100vh',
maxWidth: '100vw',
maxHeight: '100vh',
- panelClass: ['crr-slr-dialog-panel', 'fdrd-fullscreen-panel'],
+ panelClass: [
+ 'crr-slr-dialog-panel',
+ 'fdrd-fullscreen-panel'
+ ],
autoFocus: false,
disableClose: false,
hasBackdrop: true,
diff --git a/src/app/investment/bond-investment/bond-investment.component.html b/src/app/investment/bond-investment/bond-investment.component.html
new file mode 100644
index 000000000..10f310bd8
--- /dev/null
+++ b/src/app/investment/bond-investment/bond-investment.component.html
@@ -0,0 +1,1093 @@
+
+
+
+
+
+
TREASURY · INVESTMENT · GOVT SECURITY / NON-SLR / BOND / MUTUAL FUND
+
Govt. Security / Bond Entry
+
+ Purchase, Redemption, Interest Calculation, Sell, Write-off, Amortize, Yearly Amortize Process, GOI Category
+ Transfer, Investment Depreciation and Mutual Fund Transfer.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @if (activeTab === 'holdings') {
+
+
{{ instrumentLabel(bondType) }} Holdings Register
+
+
+
+ | Cert No |
+ Script |
+ Party |
+ Units |
+ Face Value |
+ Rate |
+ Purchase Date |
+ Maturity Date |
+ Accrued Interest |
+ Maturity Value |
+ Status |
+
+
+
+ @for (h of holdingsForType; track h.certNo) {
+
+ |
+ {{ h.certNo }}
+ |
+ {{ h.scriptName }} |
+ {{ h.party }} |
+ {{ h.units | number }} |
+ ₹{{ h.faceValue | number: '1.2-2' }} |
+ {{ h.interestRate }}% |
+ {{ h.purchaseDate }} |
+ {{ h.maturityDate }} |
+ ₹{{ accrued(h) | number: '1.2-2' }} |
+ ₹{{ maturity(h) | number: '1.2-2' }} |
+
+ {{ h.status }}
+ |
+
+ } @empty {
+
+ | No {{ instrumentLabel(bondType) }} holdings yet. |
+
+ }
+
+
+
+ }
+
+
+ @if (activeTab === 'purchase') {
+
+ }
+
+
+ @if (activeTab === 'redemption') {
+
+ }
+
+
+ @if (activeTab === 'interest') {
+
+
Investment - Govt./Bond Interest calculation
+
+
+
+
+
+
+
+
+
+
+
+
Holdings matching the filter ({{ intCalcTargets.length }})
+
+
+
+ | Cert No |
+ Script |
+ Type |
+ Rate |
+ Frequency |
+ Interest to Post |
+
+
+
+ @for (h of intCalcTargets; track h.certNo) {
+
+ |
+ {{ h.certNo }}
+ |
+ {{ h.scriptName }} |
+ {{ instrumentLabel(h.instrumentType) }} |
+ {{ h.interestRate }}% |
+ {{ h.interestFreq }} |
+ ₹{{ interestAccrued(h) | number: '1.2-2' }} |
+
+ } @empty {
+
+ | No holdings match the selected filters. |
+
+ }
+
+
+
+ }
+
+
+ @if (activeTab === 'sell') {
+
+ }
+
+
+ @if (activeTab === 'writeoff') {
+
+
Investment - Write Off Entry Gov/Bond
+
+
+ @if (writeoffHolding; as h) {
+
+ }
+
+
+
+
+
+
+
+ }
+
+
+ @if (activeTab === 'amortize') {
+
+ }
+
+
+ @if (activeTab === 'amortizeYearly') {
+
+ }
+
+
+ @if (activeTab === 'goiTransfer') {
+
+ }
+
+
+ @if (activeTab === 'depreciation') {
+
+ }
+
+
+ @if (activeTab === 'mfTransfer') {
+
+ }
+
+
+ @if (activeTab !== 'holdings') {
+
+
{{ instrumentLabel(bondType) }} Transaction Log
+
+
+
+ | Date |
+ Voucher No |
+ Cert No |
+ Type |
+ Particulars |
+ Amount |
+
+
+
+ @for (t of txnsForType; track t.voucherNo) {
+
+ | {{ t.txnDate }} |
+
+ {{ t.voucherNo }}
+ |
+ {{ t.certNo }} |
+ {{ t.txnType }} |
+ {{ t.particulars }} |
+ ₹{{ t.amount | number: '1.2-2' }} |
+
+ } @empty {
+
+ | No transactions yet. |
+
+ }
+
+
+
+ }
+
+
+
{{ toastMsg }}
diff --git a/src/app/investment/bond-investment/bond-investment.component.ts b/src/app/investment/bond-investment/bond-investment.component.ts
new file mode 100644
index 000000000..de971e980
--- /dev/null
+++ b/src/app/investment/bond-investment/bond-investment.component.ts
@@ -0,0 +1,1103 @@
+/**
+ * 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 } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import {
+ GOVT_SECURITY_HOLDINGS,
+ NON_SLR_HOLDINGS,
+ BOND_HOLDINGS,
+ MUTUAL_FUND_HOLDINGS,
+ ALL_TXNS,
+ SCRIPT_MASTER,
+ PARTY_MASTER,
+ SUBTYPE_MASTER,
+ BROKER_MASTER,
+ SGL_BANK_MASTER,
+ MUTUAL_FUND_PRODUCTS,
+ LIEN_REASON_MASTER,
+ CATEGORY_OPTIONS,
+ DEAL_TYPE_OPTIONS,
+ MARKET_OPTIONS,
+ PAYMENT_MODE_OPTIONS,
+ DELIVERY_MODE_OPTIONS,
+ BOND_RATING_OPTIONS,
+ INT_TYPE_OPTIONS,
+ AUTHORITY_OPTIONS,
+ InvestmentHolding,
+ InvestmentTxn,
+ InstrumentType,
+ InterestFreq,
+ INSTRUMENT_LABELS,
+ accruedInterest,
+ daysToMaturity,
+ maturityAmount,
+ addPeriod,
+ approxYtm,
+ yearsBetween,
+ todayDDMMYYYY
+} from '../investment-data';
+
+type BondType = 'GOVT_SECURITY' | 'NON_SLR' | 'BOND' | 'MUTUAL_FUND';
+type BondTab =
+ | 'holdings'
+ | 'purchase'
+ | 'redemption'
+ | 'interest'
+ | 'sell'
+ | 'writeoff'
+ | 'amortize'
+ | 'amortizeYearly'
+ | 'goiTransfer'
+ | 'depreciation'
+ | 'mfTransfer';
+
+interface BondPurchaseForm {
+ bondType: BondType;
+ dealNo: string;
+ dealDate: string;
+ dealTime: string;
+ isin: string;
+ scriptCode: string;
+ certNo: string;
+ settlementType: 'T+0' | 'T+1' | 'T+2';
+ settlementDt: string;
+ faceValue: number | null;
+ purchaseRate: number | null;
+ partyCode: string;
+ subType: string;
+ tbillGlAc: string;
+ crToAc: string;
+ premiumAmt: number | null;
+ brokerage: number | null;
+ brokerName: string;
+ tradeNo: string;
+ orderNo: string;
+ recommAuthority: string;
+ sanctionAuthority: string;
+ issueDate: string;
+ periodYear: number | null;
+ periodMonth: number | null;
+ periodDays: number | null;
+ descInv: string;
+ folioNo: string;
+ callOption: string;
+ meetDt: string;
+ resoNo: string;
+ fromDist: string;
+ toDist: string;
+ redumMatDt: string;
+ category: string;
+ intRate: number | null;
+ intType: InterestFreq;
+ intDate: string;
+ lastIntDt: string;
+ dealType: string;
+ market: string;
+ markCblo: 'YES' | 'NO';
+ amortizeAmt: number | null;
+ remarks: string;
+ lien: 'YES' | 'NO';
+ lienAmount: number | null;
+ lienReason: string;
+ deliveryMode: string;
+ bondRating: string;
+ corpBond: 'YES' | 'NO';
+ paymentMode: string;
+ sglWithBank: string;
+ description: string;
+ registerAdd: string;
+ regiCity: string;
+ regiPin: string;
+ // Mutual Fund specific
+ productName: string;
+ considerAmt: number | null;
+ tradeAmt: number | null;
+ nav: number | null;
+ holdingDays: number | null;
+ stampDuty: number | null;
+ stampDutyAc: string;
+}
+
+interface BondSellForm {
+ /** Legacy screen has SELL / REDEEM / MUTUAL FUND SELL radio at the top. */
+ mode: 'SELL' | 'REDEEM' | 'MUTUAL_FUND_SELL';
+ /** Purchase Search — locate the original purchase by Deal No or ISIN No. */
+ searchBy: 'DEAL_NO' | 'ISIN_NO';
+ searchValue: string;
+ bondType: BondType;
+ scriptCode: string;
+ certNo: string;
+ buyerCode: string;
+ subType: string;
+ isin: string;
+ tbillGlAc: string;
+ dealDate: string;
+ dealTime: string;
+ settlementType: 'T+0' | 'T+1' | 'T+2';
+ settlementDt: string;
+ faceValue: number | null;
+ sellRate: number | null;
+ tdsAmt: number | null;
+ unitVal: number | null;
+ payAmt: number | null;
+ sellUnit: number | null;
+ brokerage: number | null;
+ dbToAc: string;
+ amortizeAmt: number | null;
+ recommAuthority: string;
+ sanctionAuthority: string;
+ callMoney: boolean;
+}
+
+/**
+ * Combined Govt Security / Non-SLR / Bond / Mutual Fund treasury screen, mirroring the legacy
+ * "03 Govt./Bond Entry" submenu in full: Purchase, Redemption, Interest Calculation, Sell,
+ * Write-off, Amortize, Amortize Yearly Process, GOI Category Transfer, Investment Depreciation
+ * and Mutual Fund Transfer. Dummy in-memory data; Save appends to the local arrays.
+ */
+@Component({
+ selector: 'mifosx-bond-investment',
+ templateUrl: './bond-investment.component.html',
+ styleUrls: [
+ '../../cashier/cashier-shared.scss',
+ '../investment-shared.scss'
+ ],
+ standalone: true,
+ imports: [
+ CommonModule,
+ FormsModule
+ ]
+})
+export class BondInvestmentComponent {
+ activeTab: BondTab = 'holdings';
+ bondType: BondType = 'GOVT_SECURITY';
+ asOnDate = todayDDMMYYYY();
+
+ holdings: InvestmentHolding[] = [
+ ...GOVT_SECURITY_HOLDINGS,
+ ...NON_SLR_HOLDINGS,
+ ...BOND_HOLDINGS,
+ ...MUTUAL_FUND_HOLDINGS
+ ];
+ txns: InvestmentTxn[] = ALL_TXNS.filter((t) => ([
+ 'GOVT_SECURITY',
+ 'NON_SLR',
+ 'BOND',
+ 'MUTUAL_FUND'
+ ] as InstrumentType[]).includes(t.instrumentType));
+
+ // Master-driven dropdown sources
+ readonly scriptOptions = SCRIPT_MASTER;
+ readonly partyOptions = PARTY_MASTER;
+ readonly subTypeOptions = SUBTYPE_MASTER;
+ readonly brokerOptions = BROKER_MASTER;
+ readonly sglBankOptions = SGL_BANK_MASTER;
+ readonly mfProductOptions = MUTUAL_FUND_PRODUCTS;
+ readonly lienReasonOptions = LIEN_REASON_MASTER;
+ readonly categoryOptions = CATEGORY_OPTIONS;
+ readonly dealTypeOptions = DEAL_TYPE_OPTIONS;
+ readonly marketOptions = MARKET_OPTIONS;
+ readonly paymentModeOptions = PAYMENT_MODE_OPTIONS;
+ readonly deliveryModeOptions = DELIVERY_MODE_OPTIONS;
+ readonly bondRatingOptions = BOND_RATING_OPTIONS;
+ readonly intTypeOptions = INT_TYPE_OPTIONS;
+ readonly authorityOptions = AUTHORITY_OPTIONS;
+
+ toastMsg = '';
+ toastVisible = false;
+ private toastTimer: ReturnType
| undefined;
+
+ purchaseForm: BondPurchaseForm = this.blankPurchaseForm();
+ sellForm: BondSellForm = this.blankSellForm();
+
+ // Redemption Entry
+ redeemCertNo = '';
+ redeemDate = this.asOnDate;
+ redeemRemarks = '';
+
+ // Interest Calculation (legacy: Date / Type / Script / Deal No / ALL + Process)
+ intCalcDate = this.asOnDate;
+ intCalcType: BondType | '' = '';
+ intCalcScript = '';
+ intCalcDealNo = '';
+ intCalcAll = false;
+
+ // Write-off
+ writeoffCertNo = '';
+ writeoffReason = '';
+
+ // Amortize
+ amortizeCertNo = '';
+ amortizeAmt: number | null = null;
+
+ // Amortize Yearly Process
+ amortizeYear = String(new Date().getFullYear());
+
+ // GOI Category Transfer
+ goiCertNo = '';
+ goiFromCategory = '';
+ goiToCategory = '';
+ goiTransferDate = this.asOnDate;
+
+ // Investment Depreciation
+ depreciationDate = this.asOnDate;
+ depreciationCategory = '';
+
+ // Mutual Fund Transfer
+ mfFromCertNo = '';
+ mfToProduct = '';
+ mfTransferUnits: number | null = null;
+ mfTransferDate = this.asOnDate;
+
+ readonly bondTypeOptions: Array<{ value: BondType; label: string }> = [
+ { value: 'GOVT_SECURITY', label: 'GOVT SECURITY' },
+ { value: 'NON_SLR', label: 'NON SLR' },
+ { value: 'BOND', label: 'BOND' },
+ { value: 'MUTUAL_FUND', label: 'MUTUAL FUND' }
+ ];
+
+ setTab(tab: BondTab): void {
+ this.activeTab = tab;
+ }
+
+ setBondType(type: BondType): void {
+ this.bondType = type;
+ this.purchaseForm.bondType = type;
+ }
+
+ get holdingsForType(): InvestmentHolding[] {
+ return this.holdings.filter((h) => h.instrumentType === this.bondType);
+ }
+
+ get activeHoldingsForType(): InvestmentHolding[] {
+ return this.holdingsForType.filter((h) => h.status === 'ACTIVE');
+ }
+
+ get txnsForType(): InvestmentTxn[] {
+ return this.txns.filter((t) => t.instrumentType === this.bondType);
+ }
+
+ get scriptsForType(): typeof SCRIPT_MASTER {
+ return this.scriptOptions.filter((s) => s.instrumentType === this.purchaseForm.bondType);
+ }
+
+ instrumentLabel(type: InstrumentType): string {
+ return INSTRUMENT_LABELS[type];
+ }
+
+ private nextDealNo(): string {
+ return `2026${String(27000039 + this.holdings.length).padStart(9, '0')}`;
+ }
+
+ private blankPurchaseForm(): BondPurchaseForm {
+ const type = this.bondType ?? 'GOVT_SECURITY';
+ return {
+ bondType: type,
+ dealNo: this.nextDealNo(),
+ dealDate: this.asOnDate,
+ dealTime: '',
+ isin: '',
+ scriptCode: '',
+ certNo: '',
+ settlementType: 'T+0',
+ settlementDt: this.asOnDate,
+ faceValue: null,
+ purchaseRate: 100,
+ partyCode: '',
+ subType: type === 'GOVT_SECURITY' ? 'SLR' : type === 'NON_SLR' ? 'NON SLR' : 'CORPORATE BOND',
+ tbillGlAc: '',
+ crToAc: '310070',
+ premiumAmt: null,
+ brokerage: null,
+ brokerName: '',
+ tradeNo: '',
+ orderNo: '',
+ recommAuthority: '',
+ sanctionAuthority: '',
+ issueDate: this.asOnDate,
+ periodYear: 3,
+ periodMonth: 0,
+ periodDays: 0,
+ descInv: '',
+ folioNo: '',
+ callOption: '',
+ meetDt: '',
+ resoNo: '',
+ fromDist: '',
+ toDist: '',
+ redumMatDt: '',
+ category: '',
+ intRate: null,
+ intType: 'HALF_YEARLY',
+ intDate: this.asOnDate,
+ lastIntDt: this.asOnDate,
+ dealType: 'OUTRIGHT',
+ market: 'NDS-OM',
+ markCblo: 'NO',
+ amortizeAmt: null,
+ remarks: '',
+ lien: 'NO',
+ lienAmount: null,
+ lienReason: '',
+ deliveryMode: '',
+ bondRating: type === 'GOVT_SECURITY' ? 'SOVEREIGN' : 'AAA',
+ corpBond: type === 'BOND' ? 'YES' : 'NO',
+ paymentMode: '',
+ sglWithBank: '',
+ description: '',
+ registerAdd: '',
+ regiCity: '',
+ regiPin: '',
+ productName: '',
+ considerAmt: null,
+ tradeAmt: null,
+ nav: null,
+ holdingDays: null,
+ stampDuty: null,
+ stampDutyAc: ''
+ };
+ }
+
+ private blankSellForm(): BondSellForm {
+ return {
+ mode: 'SELL',
+ searchBy: 'DEAL_NO',
+ searchValue: '',
+ bondType: this.bondType ?? 'GOVT_SECURITY',
+ scriptCode: '',
+ certNo: '',
+ buyerCode: '',
+ subType: '',
+ isin: '',
+ tbillGlAc: '',
+ dealDate: this.asOnDate,
+ dealTime: '',
+ settlementType: 'T+0',
+ settlementDt: this.asOnDate,
+ faceValue: null,
+ sellRate: null,
+ tdsAmt: null,
+ unitVal: 100,
+ payAmt: null,
+ sellUnit: null,
+ brokerage: null,
+ dbToAc: '310070',
+ amortizeAmt: null,
+ recommAuthority: '',
+ sanctionAuthority: '',
+ callMoney: false
+ };
+ }
+
+ get isMutualFund(): boolean {
+ return this.purchaseForm.bondType === 'MUTUAL_FUND';
+ }
+
+ /** Face value for the purchase — Mutual Fund screens label it "Consider Amt" instead. */
+ get purchaseBaseAmount(): number {
+ const f = this.purchaseForm;
+ return (this.isMutualFund ? f.considerAmt : f.faceValue) || 0;
+ }
+
+ get purchaseMaturityDate(): string {
+ const f = this.purchaseForm;
+ if (f.redumMatDt) {
+ return f.redumMatDt;
+ }
+ if (!f.issueDate) {
+ return '';
+ }
+ return addPeriod(f.issueDate, f.periodYear || 0, f.periodMonth || 0, f.periodDays || 0);
+ }
+
+ get purchaseUnits(): number {
+ const f = this.purchaseForm;
+ const amt = this.purchaseBaseAmount;
+ if (this.isMutualFund) {
+ return f.nav ? +(amt / f.nav).toFixed(3) : 0;
+ }
+ return f.purchaseRate ? Math.round((amt / f.purchaseRate) * (f.purchaseRate / 100)) : 0;
+ }
+
+ /** Unit Val — the per-unit face value used by the legacy form (100 for G-Sec/Bonds, NAV for MF). */
+ get purchaseUnitVal(): number {
+ return this.isMutualFund ? this.purchaseForm.nav || 0 : 100;
+ }
+
+ /** Book Value — what the investment is carried at, i.e. amount actually paid. */
+ get purchaseBookValue(): number {
+ const f = this.purchaseForm;
+ if (this.isMutualFund) {
+ return this.purchaseBaseAmount + (f.stampDuty || 0) + (f.brokerage || 0);
+ }
+ const rate = f.purchaseRate || 100;
+ return Math.round((this.purchaseBaseAmount * rate) / 100) + (f.brokerage || 0);
+ }
+
+ get purchaseYtm(): number {
+ const f = this.purchaseForm;
+ if (!f.intRate || !this.purchaseMaturityDate) {
+ return 0;
+ }
+ return approxYtm(f.intRate, f.purchaseRate || 100, yearsBetween(f.issueDate, this.purchaseMaturityDate));
+ }
+
+ /** Accrued Interest at purchase — interest earned by the seller since the last coupon date. */
+ get purchaseAccruedInterest(): number {
+ const f = this.purchaseForm;
+ if (!f.intRate || !f.lastIntDt || !f.dealDate) {
+ return 0;
+ }
+ const yrs = Math.max(0, yearsBetween(f.lastIntDt, f.dealDate));
+ return Math.round(this.purchaseBaseAmount * (f.intRate / 100) * yrs);
+ }
+
+ /** Settlement Amt of Purchase — book value plus accrued interest payable to the seller. */
+ get purchaseSettlementAmount(): number {
+ return this.purchaseBookValue + this.purchaseAccruedInterest;
+ }
+
+ get purchaseMaturityAmount(): number {
+ const f = this.purchaseForm;
+ if (!this.purchaseBaseAmount || !this.purchaseMaturityDate) {
+ return this.purchaseBaseAmount;
+ }
+ return maturityAmount({
+ certNo: f.certNo,
+ instrumentType: f.bondType,
+ scriptCode: f.scriptCode,
+ scriptName: '',
+ party: f.partyCode,
+ subType: f.subType,
+ faceValue: this.purchaseBaseAmount,
+ purchaseRate: f.purchaseRate || 100,
+ units: this.purchaseUnits,
+ purchaseDate: f.issueDate,
+ maturityDate: this.purchaseMaturityDate,
+ interestRate: f.intRate || 0,
+ interestFreq: this.isMutualFund ? 'CUMULATIVE' : f.intType,
+ glCode: '',
+ glName: '',
+ crToAc: f.crToAc,
+ brokerCode: f.brokerName,
+ status: 'ACTIVE'
+ });
+ }
+
+ /** Fills ISIN + interest rate when a script is picked, mirroring the legacy form's lookup. */
+ onScriptChange(): void {
+ const s = this.scriptOptions.find((x) => x.scriptCode === this.purchaseForm.scriptCode);
+ if (s) {
+ this.purchaseForm.isin = s.isin;
+ }
+ }
+
+ savePurchase(): void {
+ const f = this.purchaseForm;
+ if (!f.certNo || !this.purchaseBaseAmount || !f.partyCode) {
+ this.toast('Please fill Cert No, Face Value / Consider Amt and Party Code.');
+ return;
+ }
+ const holding: InvestmentHolding = {
+ certNo: f.certNo,
+ instrumentType: f.bondType,
+ scriptCode: f.scriptCode || f.isin || f.productName,
+ scriptName: this.isMutualFund
+ ? f.productName
+ : this.scriptOptions.find((s) => s.scriptCode === f.scriptCode)?.scriptName || f.scriptCode || f.isin,
+ party: f.partyCode,
+ subType: f.subType,
+ faceValue: this.purchaseBaseAmount,
+ purchaseRate: this.isMutualFund ? f.nav || 0 : f.purchaseRate || 100,
+ units: this.purchaseUnits,
+ purchaseDate: f.issueDate,
+ maturityDate: this.purchaseMaturityDate || f.issueDate,
+ interestRate: f.intRate || 0,
+ interestFreq: this.isMutualFund ? 'CUMULATIVE' : f.intType,
+ glCode:
+ '31' +
+ (f.bondType === 'GOVT_SECURITY'
+ ? '0020'
+ : f.bondType === 'NON_SLR'
+ ? '0030'
+ : f.bondType === 'BOND'
+ ? '0040'
+ : '0050'),
+ glName: `INVESTMENT IN ${INSTRUMENT_LABELS[f.bondType].toUpperCase()}`,
+ crToAc: f.crToAc,
+ brokerCode: f.brokerName,
+ status: 'ACTIVE'
+ };
+ this.holdings = [
+ holding,
+ ...this.holdings
+ ];
+ this.txns = [
+ {
+ certNo: holding.certNo,
+ instrumentType: f.bondType,
+ txnType: 'PURCHASE',
+ voucherNo: `INV/PUR/${f.dealNo}`,
+ txnDate: f.dealDate,
+ amount: this.purchaseSettlementAmount,
+ particulars: `Purchase Deal ${f.dealNo} - ${holding.scriptName} - ${holding.party}${f.category ? ' [' + f.category + ']' : ''}`
+ },
+ ...this.txns
+ ];
+ this.toast(`${INSTRUMENT_LABELS[f.bondType]} Certificate ${holding.certNo} saved successfully.`);
+ this.purchaseForm = this.blankPurchaseForm();
+ this.activeTab = 'holdings';
+ }
+
+ cancelPurchase(): void {
+ this.purchaseForm = this.blankPurchaseForm();
+ this.toast('Entry cancelled — form reset.');
+ }
+
+ /** Legacy "Retrieve" button — loads an existing deal back into the form by Cert No. */
+ retrievePurchase(): void {
+ const h = this.holdings.find((x) => x.certNo === this.purchaseForm.certNo.trim());
+ if (!h) {
+ this.toast(`Cert No ${this.purchaseForm.certNo} not found.`);
+ return;
+ }
+ this.purchaseForm = {
+ ...this.blankPurchaseForm(),
+ bondType: h.instrumentType as BondType,
+ certNo: h.certNo,
+ scriptCode: h.scriptCode,
+ isin: this.scriptOptions.find((s) => s.scriptCode === h.scriptCode)?.isin || '',
+ partyCode: h.party,
+ subType: h.subType,
+ faceValue: h.faceValue,
+ considerAmt: h.faceValue,
+ purchaseRate: h.purchaseRate,
+ nav: h.purchaseRate,
+ issueDate: h.purchaseDate,
+ redumMatDt: h.maturityDate,
+ intRate: h.interestRate,
+ intType: h.interestFreq,
+ brokerName: h.brokerCode,
+ crToAc: h.crToAc,
+ productName: h.scriptName
+ };
+ this.bondType = h.instrumentType as BondType;
+ this.toast(`Deal for Cert No ${h.certNo} retrieved.`);
+ }
+
+ dropPurchase(): void {
+ const certNo = this.purchaseForm.certNo;
+ if (!this.holdings.some((h) => h.certNo === certNo)) {
+ this.toast('Nothing to drop — this deal is not yet saved.');
+ return;
+ }
+ this.holdings = this.holdings.filter((h) => h.certNo !== certNo);
+ this.txns = this.txns.filter((t) => t.certNo !== certNo);
+ this.purchaseForm = this.blankPurchaseForm();
+ this.toast(`Cert No ${certNo} dropped.`);
+ }
+
+ exitPurchase(): void {
+ this.purchaseForm = this.blankPurchaseForm();
+ this.activeTab = 'holdings';
+ }
+
+ // -------------------- Redemption Entry --------------------
+ get redeemHolding(): InvestmentHolding | undefined {
+ return this.holdingsForType.find((h) => h.certNo === this.redeemCertNo);
+ }
+
+ get redeemAmount(): number {
+ const h = this.redeemHolding;
+ return h ? maturityAmount(h) + accruedInterest(h, this.redeemDate) - h.faceValue + h.faceValue : 0;
+ }
+
+ saveRedemption(): void {
+ const h = this.redeemHolding;
+ if (!h) {
+ this.toast('Select a certificate to redeem.');
+ return;
+ }
+ h.status = 'MATURED';
+ this.txns = [
+ {
+ certNo: h.certNo,
+ instrumentType: h.instrumentType,
+ txnType: 'REDEEM',
+ voucherNo: `INV/RED/${h.certNo}`,
+ txnDate: this.redeemDate,
+ amount: this.redeemAmount,
+ particulars: `Redemption on maturity - ${h.scriptName}${this.redeemRemarks ? ' - ' + this.redeemRemarks : ''}`
+ },
+ ...this.txns
+ ];
+ this.toast(`${h.certNo} redeemed. Proceeds ₹${this.redeemAmount.toLocaleString('en-IN')}.`);
+ this.redeemCertNo = '';
+ this.redeemRemarks = '';
+ this.activeTab = 'holdings';
+ }
+
+ // -------------------- Interest calculation --------------------
+ /** Holdings the Interest Calculation "Process" run would cover, given the filters. */
+ get intCalcTargets(): InvestmentHolding[] {
+ if (this.intCalcAll) {
+ return this.holdings.filter((h) => h.status === 'ACTIVE');
+ }
+ return this.holdings.filter((h) => {
+ if (h.status !== 'ACTIVE') return false;
+ if (this.intCalcType && h.instrumentType !== this.intCalcType) return false;
+ if (this.intCalcScript && h.scriptCode !== this.intCalcScript) return false;
+ if (this.intCalcDealNo && h.certNo !== this.intCalcDealNo.trim()) return false;
+ return true;
+ });
+ }
+
+ interestAccrued(h: InvestmentHolding): number {
+ return accruedInterest(h, this.intCalcDate || this.asOnDate);
+ }
+
+ processInterest(): void {
+ const targets = this.intCalcTargets;
+ if (targets.length === 0) {
+ this.toast('No holdings match the selected filters.');
+ return;
+ }
+ const newTxns: InvestmentTxn[] = targets.map((h, i) => ({
+ certNo: h.certNo,
+ instrumentType: h.instrumentType,
+ txnType: 'INTEREST',
+ voucherNo: `INV/INT/${h.certNo}-${this.txns.length + i + 1}`,
+ txnDate: this.intCalcDate || this.asOnDate,
+ amount: this.interestAccrued(h),
+ particulars: `Interest calculated - ${h.scriptName}`
+ }));
+ this.txns = [
+ ...newTxns,
+ ...this.txns
+ ];
+ const total = newTxns.reduce((s, t) => s + t.amount, 0);
+ this.toast(`Interest processed for ${targets.length} holding(s). Total ₹${total.toLocaleString('en-IN')}.`);
+ }
+
+ // -------------------- Sell / Redeem Entry --------------------
+ /** Locates the original purchase by Deal No / Cert No or ISIN and fills the sell form. */
+ searchPurchaseForSell(): void {
+ const val = this.sellForm.searchValue.trim();
+ if (!val) {
+ this.toast('Enter a Deal No / Cert No or ISIN to search.');
+ return;
+ }
+ const h =
+ this.sellForm.searchBy === 'DEAL_NO'
+ ? this.holdings.find((x) => x.certNo === val)
+ : this.holdings.find((x) => this.scriptOptions.find((s) => s.scriptCode === x.scriptCode)?.isin === val);
+ if (!h) {
+ this.toast('No matching purchase found.');
+ return;
+ }
+ this.sellForm = {
+ ...this.sellForm,
+ bondType: h.instrumentType as BondType,
+ scriptCode: h.scriptCode,
+ certNo: h.certNo,
+ subType: h.subType,
+ isin: this.scriptOptions.find((s) => s.scriptCode === h.scriptCode)?.isin || '',
+ faceValue: h.faceValue,
+ unitVal: h.instrumentType === 'MUTUAL_FUND' ? h.purchaseRate : 100,
+ sellUnit: h.units
+ };
+ this.toast(`Purchase ${h.certNo} loaded.`);
+ }
+
+ get sellHolding(): InvestmentHolding | undefined {
+ return this.holdings.find((h) => h.certNo === this.sellForm.certNo);
+ }
+
+ /** Consideration = units sold × sell rate, scaled by the instrument's unit convention. */
+ get sellConsideration(): number {
+ const f = this.sellForm;
+ if (!f.sellUnit || !f.sellRate) {
+ return 0;
+ }
+ const perUnitDivisor = f.mode === 'MUTUAL_FUND_SELL' ? 1 : 100;
+ return Math.round((f.sellUnit * f.sellRate) / perUnitDivisor);
+ }
+
+ /** Sale Diff — profit/loss versus what the holding was bought at. */
+ get sellSaleDiff(): number {
+ const h = this.sellHolding;
+ const f = this.sellForm;
+ if (!h || !f.sellUnit || !f.sellRate) {
+ return 0;
+ }
+ const perUnitDivisor = f.mode === 'MUTUAL_FUND_SELL' ? 1 : 100;
+ const cost = Math.round((f.sellUnit * h.purchaseRate) / perUnitDivisor);
+ return this.sellConsideration - cost;
+ }
+
+ /** Premium Loss — the unamortised premium written off when a holding is sold early. */
+ get sellPremiumLoss(): number {
+ const h = this.sellHolding;
+ if (!h || h.purchaseRate <= 100 || h.instrumentType === 'MUTUAL_FUND') {
+ return 0;
+ }
+ return Math.round((h.faceValue * (h.purchaseRate - 100)) / 100);
+ }
+
+ get sellAccruedInterest(): number {
+ const h = this.sellHolding;
+ return h ? accruedInterest(h, this.sellForm.dealDate || this.asOnDate) : 0;
+ }
+
+ get sellAccruedDays(): number {
+ const h = this.sellHolding;
+ if (!h) {
+ return 0;
+ }
+ return Math.max(0, Math.round(yearsBetween(h.purchaseDate, this.sellForm.dealDate || this.asOnDate) * 365));
+ }
+
+ /** Pay Amt — net amount receivable: consideration + accrued interest − TDS − brokerage. */
+ get sellPayAmount(): number {
+ const f = this.sellForm;
+ return this.sellConsideration + this.sellAccruedInterest - (f.tdsAmt || 0) - (f.brokerage || 0);
+ }
+
+ saveSell(): void {
+ const f = this.sellForm;
+ const h = this.sellHolding;
+ if (!h || !f.sellRate || !f.sellUnit) {
+ this.toast('Load a purchase, then enter Sell Rate and Sell Unit.');
+ return;
+ }
+ if (f.sellUnit > h.units) {
+ this.toast(`Sell Unit cannot exceed units held (${h.units}).`);
+ return;
+ }
+ h.units -= f.sellUnit;
+ h.faceValue = Math.round(h.units * (f.unitVal || 100));
+ if (h.units <= 0) {
+ h.status = f.mode === 'REDEEM' ? 'MATURED' : 'SOLD';
+ }
+ this.txns = [
+ {
+ certNo: h.certNo,
+ instrumentType: h.instrumentType,
+ txnType: f.mode === 'REDEEM' ? 'REDEEM' : 'SELL',
+ voucherNo: `INV/${f.mode === 'REDEEM' ? 'RED' : 'SEL'}/${h.certNo}-${this.txns.length + 1}`,
+ txnDate: f.dealDate,
+ amount: this.sellPayAmount,
+ particulars: `${f.mode.replace(/_/g, ' ')} - ${h.scriptName} @ ${f.sellRate}${f.buyerCode ? ' to ' + f.buyerCode : ''}${f.callMoney ? ' [CALL MONEY]' : ''}`
+ },
+ ...this.txns
+ ];
+ this.toast(
+ `${h.certNo} ${f.mode === 'REDEEM' ? 'redeemed' : 'sold'}. Pay Amt ₹${this.sellPayAmount.toLocaleString('en-IN')}.`
+ );
+ this.sellForm = this.blankSellForm();
+ this.activeTab = 'holdings';
+ }
+
+ cancelSell(): void {
+ this.sellForm = this.blankSellForm();
+ this.toast('Sell entry cancelled — form reset.');
+ }
+
+ exitSell(): void {
+ this.sellForm = this.blankSellForm();
+ this.activeTab = 'holdings';
+ }
+
+ // -------------------- Write-off --------------------
+ get writeoffHolding(): InvestmentHolding | undefined {
+ return this.holdingsForType.find((h) => h.certNo === this.writeoffCertNo);
+ }
+
+ saveWriteoff(): void {
+ const h = this.writeoffHolding;
+ if (!h || !this.writeoffReason) {
+ this.toast('Select a certificate and enter a write-off reason.');
+ return;
+ }
+ h.status = 'WRITTEN_OFF';
+ this.txns = [
+ {
+ certNo: h.certNo,
+ instrumentType: h.instrumentType,
+ txnType: 'WRITE_OFF',
+ voucherNo: `INV/WOF/${h.certNo}`,
+ txnDate: this.asOnDate,
+ amount: h.faceValue,
+ particulars: `Written off - ${this.writeoffReason}`
+ },
+ ...this.txns
+ ];
+ this.toast(`${h.certNo} written off.`);
+ this.writeoffCertNo = '';
+ this.writeoffReason = '';
+ this.activeTab = 'holdings';
+ }
+
+ // -------------------- Amortize --------------------
+ get amortizeHolding(): InvestmentHolding | undefined {
+ return this.holdingsForType.find((h) => h.certNo === this.amortizeCertNo);
+ }
+
+ /** Straight-line premium amortisation for one year on the selected holding. */
+ get suggestedAmortizeAmt(): number {
+ const h = this.amortizeHolding;
+ if (!h || h.purchaseRate <= 100) {
+ return 0;
+ }
+ const years = Math.max(1, yearsBetween(h.purchaseDate, h.maturityDate));
+ return Math.round((h.faceValue * (h.purchaseRate - 100)) / 100 / years);
+ }
+
+ saveAmortize(): void {
+ const h = this.amortizeHolding;
+ const amt = this.amortizeAmt ?? this.suggestedAmortizeAmt;
+ if (!h || !amt) {
+ this.toast('Select a certificate and enter the amortization amount.');
+ return;
+ }
+ this.txns = [
+ {
+ certNo: h.certNo,
+ instrumentType: h.instrumentType,
+ txnType: 'AMORTIZE',
+ voucherNo: `INV/AMZ/${h.certNo}-${this.txns.length + 1}`,
+ txnDate: this.asOnDate,
+ amount: amt,
+ particulars: `Premium/discount amortized - ${h.scriptName}`
+ },
+ ...this.txns
+ ];
+ this.toast(`Amortization of ₹${amt.toLocaleString('en-IN')} booked for ${h.certNo}.`);
+ this.amortizeCertNo = '';
+ this.amortizeAmt = null;
+ }
+
+ // -------------------- Amortize Yearly Process --------------------
+ /** All premium-carrying holdings the yearly batch would amortise. */
+ get amortizeYearlyTargets(): Array<{ holding: InvestmentHolding; amount: number }> {
+ return this.holdings
+ .filter((h) => h.status === 'ACTIVE' && h.purchaseRate > 100 && h.instrumentType !== 'MUTUAL_FUND')
+ .map((h) => {
+ const years = Math.max(1, yearsBetween(h.purchaseDate, h.maturityDate));
+ return { holding: h, amount: Math.round((h.faceValue * (h.purchaseRate - 100)) / 100 / years) };
+ });
+ }
+
+ get amortizeYearlyTotal(): number {
+ return this.amortizeYearlyTargets.reduce((s, t) => s + t.amount, 0);
+ }
+
+ runAmortizeYearly(): void {
+ const targets = this.amortizeYearlyTargets;
+ if (targets.length === 0) {
+ this.toast('No premium-carrying holdings to amortise.');
+ return;
+ }
+ const newTxns: InvestmentTxn[] = targets.map((t, i) => ({
+ certNo: t.holding.certNo,
+ instrumentType: t.holding.instrumentType,
+ txnType: 'AMORTIZE',
+ voucherNo: `INV/AMZ-YR/${this.amortizeYear}-${i + 1}`,
+ txnDate: this.asOnDate,
+ amount: t.amount,
+ particulars: `Yearly amortization process ${this.amortizeYear} - ${t.holding.scriptName}`
+ }));
+ this.txns = [
+ ...newTxns,
+ ...this.txns
+ ];
+ this.toast(
+ `Yearly amortization run for ${targets.length} holding(s). Total ₹${this.amortizeYearlyTotal.toLocaleString('en-IN')}.`
+ );
+ }
+
+ // -------------------- GOI Category Transfer --------------------
+ get goiHolding(): InvestmentHolding | undefined {
+ return this.holdings.find((h) => h.certNo === this.goiCertNo);
+ }
+
+ saveGoiTransfer(): void {
+ const h = this.goiHolding;
+ if (!h || !this.goiFromCategory || !this.goiToCategory) {
+ this.toast('Select a certificate and both From / To categories.');
+ return;
+ }
+ if (this.goiFromCategory === this.goiToCategory) {
+ this.toast('From and To categories must differ.');
+ return;
+ }
+ this.txns = [
+ {
+ certNo: h.certNo,
+ instrumentType: h.instrumentType,
+ txnType: 'AMORTIZE',
+ voucherNo: `INV/GOI-TRF/${h.certNo}-${this.txns.length + 1}`,
+ txnDate: this.goiTransferDate,
+ amount: h.faceValue,
+ particulars: `GOI category transfer ${this.goiFromCategory} → ${this.goiToCategory} - ${h.scriptName}`
+ },
+ ...this.txns
+ ];
+ this.toast(`${h.certNo} transferred from ${this.goiFromCategory} to ${this.goiToCategory}.`);
+ this.goiCertNo = '';
+ this.goiFromCategory = '';
+ this.goiToCategory = '';
+ }
+
+ // -------------------- Investment Depreciation --------------------
+ /** AFS/HFT holdings whose market price is below book value must be marked down (RBI norms). */
+ get depreciationTargets(): Array<{ holding: InvestmentHolding; marketPrice: number; depreciation: number }> {
+ return this.holdings
+ .filter((h) => h.status === 'ACTIVE' && h.instrumentType !== 'MUTUAL_FUND')
+ .map((h) => {
+ const md = this.scriptOptions.find((s) => s.scriptCode === h.scriptCode);
+ const marketPrice = md ? h.purchaseRate * 0.995 : h.purchaseRate;
+ const depreciation =
+ marketPrice < h.purchaseRate ? Math.round((h.faceValue * (h.purchaseRate - marketPrice)) / 100) : 0;
+ return { holding: h, marketPrice: +marketPrice.toFixed(2), depreciation };
+ })
+ .filter((r) => r.depreciation > 0);
+ }
+
+ get depreciationTotal(): number {
+ return this.depreciationTargets.reduce((s, r) => s + r.depreciation, 0);
+ }
+
+ runDepreciation(): void {
+ const targets = this.depreciationTargets;
+ if (targets.length === 0) {
+ this.toast('No holdings require depreciation provisioning.');
+ return;
+ }
+ const newTxns: InvestmentTxn[] = targets.map((t, i) => ({
+ certNo: t.holding.certNo,
+ instrumentType: t.holding.instrumentType,
+ txnType: 'AMORTIZE',
+ voucherNo: `INV/DEPR/${this.depreciationDate.replace(/-/g, '')}-${i + 1}`,
+ txnDate: this.depreciationDate,
+ amount: t.depreciation,
+ particulars: `Investment depreciation provision - ${t.holding.scriptName} (market ${t.marketPrice})`
+ }));
+ this.txns = [
+ ...newTxns,
+ ...this.txns
+ ];
+ this.toast(
+ `Depreciation provisioned for ${targets.length} holding(s). Total ₹${this.depreciationTotal.toLocaleString('en-IN')}.`
+ );
+ }
+
+ // -------------------- Mutual Fund Transfer --------------------
+ get mfHoldings(): InvestmentHolding[] {
+ return this.holdings.filter((h) => h.instrumentType === 'MUTUAL_FUND' && h.status === 'ACTIVE');
+ }
+
+ get mfFromHolding(): InvestmentHolding | undefined {
+ return this.holdings.find((h) => h.certNo === this.mfFromCertNo);
+ }
+
+ get mfTransferValue(): number {
+ const h = this.mfFromHolding;
+ if (!h || !this.mfTransferUnits) {
+ return 0;
+ }
+ return Math.round(this.mfTransferUnits * h.purchaseRate);
+ }
+
+ saveMfTransfer(): void {
+ const h = this.mfFromHolding;
+ if (!h || !this.mfToProduct || !this.mfTransferUnits) {
+ this.toast('Select source holding, target product and units to transfer.');
+ return;
+ }
+ if (this.mfTransferUnits > h.units) {
+ this.toast(`Units cannot exceed units held (${h.units}).`);
+ return;
+ }
+ const value = this.mfTransferValue;
+ h.units = +(h.units - this.mfTransferUnits).toFixed(3);
+ h.faceValue = Math.round(h.units * h.purchaseRate);
+ if (h.units <= 0) {
+ h.status = 'SOLD';
+ }
+ const product = this.mfProductOptions.find((p) => p.productName === this.mfToProduct);
+ const newCert = `MF-${5000 + this.holdings.filter((x) => x.instrumentType === 'MUTUAL_FUND').length + 1}`;
+ this.holdings = [
+ {
+ certNo: newCert,
+ instrumentType: 'MUTUAL_FUND',
+ scriptCode: product?.productCode || this.mfToProduct,
+ scriptName: this.mfToProduct,
+ party: product?.amcName || '',
+ subType: product?.category || 'DEBT',
+ faceValue: value,
+ purchaseRate: h.purchaseRate,
+ units: this.mfTransferUnits,
+ purchaseDate: this.mfTransferDate,
+ maturityDate: addPeriod(this.mfTransferDate, 1, 0),
+ interestRate: h.interestRate,
+ interestFreq: 'CUMULATIVE',
+ glCode: '310050',
+ glName: 'INVESTMENT IN MUTUAL FUNDS',
+ crToAc: h.crToAc,
+ brokerCode: '',
+ status: 'ACTIVE'
+ },
+ ...this.holdings
+ ];
+ this.txns = [
+ {
+ certNo: newCert,
+ instrumentType: 'MUTUAL_FUND',
+ txnType: 'PURCHASE',
+ voucherNo: `INV/MF-TRF/${newCert}`,
+ txnDate: this.mfTransferDate,
+ amount: value,
+ particulars: `Mutual Fund switch from ${h.scriptName} to ${this.mfToProduct} (${this.mfTransferUnits} units)`
+ },
+ ...this.txns
+ ];
+ this.toast(`Transferred ${this.mfTransferUnits} units to ${this.mfToProduct} as ${newCert}.`);
+ this.mfFromCertNo = '';
+ this.mfToProduct = '';
+ this.mfTransferUnits = null;
+ }
+
+ // -------------------- Helpers --------------------
+ accrued(h: InvestmentHolding): number {
+ return accruedInterest(h, this.asOnDate);
+ }
+
+ maturity(h: InvestmentHolding): number {
+ return maturityAmount(h);
+ }
+
+ daysLeft(h: InvestmentHolding): number {
+ return daysToMaturity(h, this.asOnDate);
+ }
+
+ statusClass(h: InvestmentHolding): string {
+ return `inv-badge inv-badge--${h.status.toLowerCase()}`;
+ }
+
+ private toast(msg: string): void {
+ this.toastMsg = msg;
+ this.toastVisible = true;
+ if (this.toastTimer) {
+ clearTimeout(this.toastTimer);
+ }
+ this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
+ }
+}
diff --git a/src/app/investment/fd-investment/fd-investment.component.html b/src/app/investment/fd-investment/fd-investment.component.html
new file mode 100644
index 000000000..bac98204f
--- /dev/null
+++ b/src/app/investment/fd-investment/fd-investment.component.html
@@ -0,0 +1,472 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @if (activeTab === 'holdings') {
+
+
FD Holdings Register
+
+
+
+ | Cert No |
+ Party |
+ Type |
+ Face Value |
+ Rate |
+ Purchase Date |
+ Maturity Date |
+ Accrued Interest |
+ Maturity Value |
+ Status |
+
+
+
+ @for (h of holdings; track h.certNo) {
+
+ |
+ {{ h.certNo }}
+ |
+ {{ h.party }} |
+ {{ h.interestFreq === 'CUMULATIVE' ? 'Cumulative' : 'Payout' }} |
+ ₹{{ h.faceValue | number: '1.2-2' }} |
+ {{ h.interestRate }}% |
+ {{ h.purchaseDate }} |
+ {{ h.maturityDate }} |
+ ₹{{ accrued(h) | number: '1.2-2' }} |
+ ₹{{ maturity(h) | number: '1.2-2' }} |
+
+ {{ h.status }}
+ |
+
+ }
+
+
+
+ }
+
+
+ @if (activeTab === 'purchase') {
+
+ }
+
+
+ @if (activeTab === 'interest') {
+
+ }
+
+
+ @if (activeTab === 'sell') {
+
+ }
+
+
+ @if (activeTab === 'writeoff') {
+
+
Investment - FD Write Off Entry
+
+
+ @if (writeoffHolding; as h) {
+
+ }
+
+
+
+
+
+
+ }
+
+
+ @if (activeTab === 'partpayment') {
+
+
Investment - FD Part Payment Entry
+
+
+ @if (partPayHolding; as h) {
+
+ }
+
+
+
+
+
+
+ }
+
+ @if (activeTab !== 'holdings') {
+
+
FD Transaction Log
+
+
+
+ | Date |
+ Voucher No |
+ Cert No |
+ Type |
+ Particulars |
+ Amount |
+
+
+
+ @for (t of txns; track t.voucherNo) {
+
+ | {{ t.txnDate }} |
+
+ {{ t.voucherNo }}
+ |
+ {{ t.certNo }} |
+ {{ t.txnType }} |
+ {{ t.particulars }} |
+ ₹{{ t.amount | number: '1.2-2' }} |
+
+ }
+
+
+
+ }
+
+
+{{ toastMsg }}
diff --git a/src/app/investment/fd-investment/fd-investment.component.ts b/src/app/investment/fd-investment/fd-investment.component.ts
new file mode 100644
index 000000000..739adf032
--- /dev/null
+++ b/src/app/investment/fd-investment/fd-investment.component.ts
@@ -0,0 +1,461 @@
+/**
+ * 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 } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import {
+ FD_HOLDINGS,
+ ALL_TXNS,
+ PARTY_MASTER,
+ SUBTYPE_MASTER,
+ FD_TYPE_OPTIONS,
+ INT_TYPE_OPTIONS,
+ LIEN_REASON_MASTER,
+ InvestmentHolding,
+ InvestmentTxn,
+ InterestFreq,
+ accruedInterest,
+ daysToMaturity,
+ maturityAmount,
+ addPeriod,
+ todayDDMMYYYY
+} from '../investment-data';
+
+type FdTab = 'holdings' | 'purchase' | 'interest' | 'sell' | 'writeoff' | 'partpayment';
+
+interface FdPurchaseForm {
+ tranType: 'FRESH' | 'RENEWAL';
+ oldCertNo: string;
+ fdType: string;
+ /** GENERAL uses the period (Y/M/D) fields; DATEWISE lets the user type the maturity date directly. */
+ matureAmtCalcType: 'GENERAL' | 'DATEWISE';
+ certNo: string;
+ certDate: string;
+ party: string;
+ subParty: string;
+ glAcNo: string;
+ glAcName: string;
+ subType: string;
+ receiptAdvice: 'RECEIPT' | 'ADVICE';
+ transactionDate: string;
+ depositDate: string;
+ depositAmount: number | null;
+ interestFreq: InterestFreq;
+ interestDate: string;
+ interestRate: number | null;
+ periodYear: number | null;
+ periodMonth: number | null;
+ periodDays: number | null;
+ /** Only used when matureAmtCalcType === 'DATEWISE'. */
+ manualMaturityDate: string;
+ resolutionNo: string;
+ crBranchCode: string;
+ crToAc: string;
+ crToAcName: string;
+ intReceivableCode: string;
+ lien: 'YES' | 'NO';
+ lienAmt: number | null;
+ lienReason: string;
+ classification: string;
+ collable: 'YES' | 'NO';
+}
+
+/**
+ * FD (Fixed Deposit) treasury investment screen — Holdings register + Purchase / Interest /
+ * Sell-Premature / Write-off / Part-Payment entry tabs, modelled on the legacy "Investment - FD
+ * Purchase Entry" form. Dummy in-memory data only; Save appends to the local holdings/txns
+ * arrays so the demo feels interactive without any backend.
+ */
+@Component({
+ selector: 'mifosx-fd-investment',
+ templateUrl: './fd-investment.component.html',
+ styleUrls: [
+ '../../cashier/cashier-shared.scss',
+ '../investment-shared.scss'
+ ],
+ standalone: true,
+ imports: [
+ CommonModule,
+ FormsModule
+ ]
+})
+export class FdInvestmentComponent {
+ activeTab: FdTab = 'holdings';
+ asOnDate = todayDDMMYYYY();
+
+ holdings: InvestmentHolding[] = [...FD_HOLDINGS];
+ txns: InvestmentTxn[] = ALL_TXNS.filter((t) => t.instrumentType === 'FD');
+
+ readonly partyOptions = PARTY_MASTER;
+ readonly subTypeOptions = SUBTYPE_MASTER;
+ readonly fdTypeOptions = FD_TYPE_OPTIONS;
+ readonly intTypeOptions = INT_TYPE_OPTIONS;
+ readonly lienReasonOptions = LIEN_REASON_MASTER;
+ readonly branchOptions = [
+ 'HEAD OFFICE',
+ 'BRANCH 1',
+ 'BRANCH 2'
+ ];
+
+ toastMsg = '';
+ toastVisible = false;
+ private toastTimer: ReturnType | undefined;
+
+ purchaseForm: FdPurchaseForm = this.blankPurchaseForm();
+ /** Certificate No typed into the Purchase Entry's Search box (legacy "Search" button). */
+ searchCertNo = '';
+
+ interestCertNo = '';
+ sellCertNo = '';
+ sellDate = this.asOnDate;
+ sellRemark = '';
+ writeoffCertNo = '';
+ writeoffReason = '';
+ partPayCertNo = '';
+ partPayAmount: number | null = null;
+
+ setTab(tab: FdTab): void {
+ this.activeTab = tab;
+ }
+
+ get activeHoldings(): InvestmentHolding[] {
+ return this.holdings.filter((h) => h.status === 'ACTIVE');
+ }
+
+ private nextCertNo(): string {
+ const nums = this.holdings.map((h) => parseInt(h.certNo, 10)).filter((n) => !isNaN(n));
+ return String((nums.length ? Math.max(...nums) : 15619) + 1);
+ }
+
+ private blankPurchaseForm(): FdPurchaseForm {
+ return {
+ tranType: 'FRESH',
+ oldCertNo: '',
+ fdType: 'CUMMULATIVE',
+ matureAmtCalcType: 'GENERAL',
+ certNo: this.nextCertNo(),
+ certDate: this.asOnDate,
+ party: '',
+ subParty: '',
+ glAcNo: '320020',
+ glAcName: 'FIXED DEPO. CO-OPER. BANK MAIN',
+ subType: 'NON SLR',
+ receiptAdvice: 'RECEIPT',
+ transactionDate: this.asOnDate,
+ depositDate: this.asOnDate,
+ depositAmount: null,
+ interestFreq: 'CUMULATIVE',
+ interestDate: this.asOnDate,
+ interestRate: null,
+ periodYear: 2,
+ periodMonth: 0,
+ periodDays: 0,
+ manualMaturityDate: '',
+ resolutionNo: '',
+ crBranchCode: 'HEAD OFFICE',
+ crToAc: '310070',
+ crToAcName: 'CURRENT A/C CO-OP. BANK MAIN BR.41',
+ intReceivableCode: '320020',
+ lien: 'NO',
+ lienAmt: null,
+ lienReason: '',
+ classification: '',
+ collable: 'NO'
+ };
+ }
+
+ get purchaseMaturityDate(): string {
+ const f = this.purchaseForm;
+ if (f.matureAmtCalcType === 'DATEWISE') {
+ return f.manualMaturityDate;
+ }
+ if (!f.depositDate) {
+ return '';
+ }
+ const base = addPeriod(f.depositDate, f.periodYear || 0, f.periodMonth || 0);
+ return f.periodDays ? addPeriod(base, 0, 0, f.periodDays) : base;
+ }
+
+ get purchaseMaturityAmount(): number {
+ const f = this.purchaseForm;
+ if (!f.depositAmount || !f.interestRate || !this.purchaseMaturityDate) {
+ return 0;
+ }
+ return maturityAmount({
+ certNo: f.certNo,
+ instrumentType: 'FD',
+ scriptCode: '',
+ scriptName: '',
+ party: f.party,
+ subType: f.subType,
+ faceValue: f.depositAmount,
+ purchaseRate: 100,
+ units: 1,
+ purchaseDate: f.depositDate,
+ maturityDate: this.purchaseMaturityDate,
+ interestRate: f.interestRate,
+ interestFreq: f.interestFreq,
+ glCode: f.glAcNo,
+ glName: '',
+ crToAc: f.crToAc,
+ brokerCode: '',
+ status: 'ACTIVE'
+ });
+ }
+
+ savePurchase(): void {
+ const f = this.purchaseForm;
+ if (!f.party || !f.depositAmount || !f.interestRate || !this.purchaseMaturityDate) {
+ this.toast('Please fill Party, Deposit Amount, Interest Rate and Period.');
+ return;
+ }
+ const holding: InvestmentHolding = {
+ certNo: f.certNo,
+ instrumentType: 'FD',
+ scriptCode: f.interestFreq === 'CUMULATIVE' ? 'FD-CUM' : 'FD-MIP',
+ scriptName: f.fdType ? `${f.fdType} Fixed Deposit` : 'Fixed Deposit',
+ party: f.party,
+ subType: f.subType,
+ faceValue: f.depositAmount,
+ purchaseRate: 100,
+ units: 1,
+ purchaseDate: f.depositDate,
+ maturityDate: this.purchaseMaturityDate,
+ interestRate: f.interestRate,
+ interestFreq: f.interestFreq,
+ glCode: f.glAcNo,
+ glName: f.glAcName,
+ crToAc: f.crToAc,
+ brokerCode: '',
+ status: 'ACTIVE'
+ };
+ this.holdings = [
+ holding,
+ ...this.holdings
+ ];
+ this.txns = [
+ {
+ certNo: holding.certNo,
+ instrumentType: 'FD',
+ txnType: 'PURCHASE',
+ voucherNo: `INV/PUR/FD-${holding.certNo}`,
+ txnDate: f.transactionDate,
+ amount: holding.faceValue,
+ particulars: `${f.tranType === 'RENEWAL' ? 'Renewal' : 'Fresh'} FD Purchase - ${holding.party}`
+ },
+ ...this.txns
+ ];
+ this.toast(`FD Certificate No. ${holding.certNo} saved successfully.`);
+ this.purchaseForm = this.blankPurchaseForm();
+ this.activeTab = 'holdings';
+ }
+
+ cancelPurchase(): void {
+ this.purchaseForm = this.blankPurchaseForm();
+ this.toast('Entry cancelled — form reset.');
+ }
+
+ /** Legacy "Search" button — loads an existing certificate's values back into the form. */
+ searchPurchase(): void {
+ const h = this.holdings.find((x) => x.certNo === this.searchCertNo.trim());
+ if (!h) {
+ this.toast(`Certificate No. ${this.searchCertNo} not found.`);
+ return;
+ }
+ this.purchaseForm = {
+ ...this.blankPurchaseForm(),
+ tranType: 'FRESH',
+ certNo: h.certNo,
+ certDate: h.purchaseDate,
+ party: h.party,
+ subType: h.subType,
+ glAcNo: h.glCode,
+ glAcName: h.glName,
+ transactionDate: h.purchaseDate,
+ depositDate: h.purchaseDate,
+ depositAmount: h.faceValue,
+ interestFreq: h.interestFreq,
+ interestDate: h.purchaseDate,
+ interestRate: h.interestRate,
+ matureAmtCalcType: 'DATEWISE',
+ manualMaturityDate: h.maturityDate,
+ crToAc: h.crToAc
+ };
+ this.toast(`Certificate ${h.certNo} loaded.`);
+ }
+
+ /** Legacy "Drop" button — removes the loaded certificate from the register. */
+ dropPurchase(): void {
+ const certNo = this.purchaseForm.certNo;
+ const exists = this.holdings.some((h) => h.certNo === certNo);
+ if (!exists) {
+ this.toast('Nothing to drop — this certificate is not yet saved.');
+ return;
+ }
+ this.holdings = this.holdings.filter((h) => h.certNo !== certNo);
+ this.txns = this.txns.filter((t) => t.certNo !== certNo);
+ this.purchaseForm = this.blankPurchaseForm();
+ this.toast(`Certificate ${certNo} dropped.`);
+ }
+
+ /** Legacy "Exit" button — closes the entry form and returns to the register. */
+ exitPurchase(): void {
+ this.purchaseForm = this.blankPurchaseForm();
+ this.activeTab = 'holdings';
+ }
+
+ // -------------------- Interest --------------------
+ get interestHolding(): InvestmentHolding | undefined {
+ return this.holdings.find((h) => h.certNo === this.interestCertNo);
+ }
+
+ interestAccrued(h: InvestmentHolding): number {
+ return accruedInterest(h, this.asOnDate);
+ }
+
+ postInterest(): void {
+ const h = this.interestHolding;
+ if (!h) {
+ this.toast('Select a certificate first.');
+ return;
+ }
+ const amt = this.interestAccrued(h);
+ this.txns = [
+ {
+ certNo: h.certNo,
+ instrumentType: 'FD',
+ txnType: 'INTEREST',
+ voucherNo: `INV/INT/FD-${h.certNo}-${this.txns.length + 1}`,
+ txnDate: this.asOnDate,
+ amount: amt,
+ particulars: `Interest posted - ${h.party} (${h.interestFreq})`
+ },
+ ...this.txns
+ ];
+ this.toast(`Interest of ₹${amt.toLocaleString('en-IN')} posted for Certificate ${h.certNo}.`);
+ this.interestCertNo = '';
+ }
+
+ // -------------------- Sell / Premature --------------------
+ get sellHolding(): InvestmentHolding | undefined {
+ return this.holdings.find((h) => h.certNo === this.sellCertNo);
+ }
+
+ saveSell(): void {
+ const h = this.sellHolding;
+ if (!h || !this.sellDate) {
+ this.toast('Select a certificate and sell/premature date.');
+ return;
+ }
+ const payout = h.faceValue + accruedInterest(h, this.sellDate);
+ h.status = 'SOLD';
+ this.txns = [
+ {
+ certNo: h.certNo,
+ instrumentType: 'FD',
+ txnType: 'SELL',
+ voucherNo: `INV/SEL/FD-${h.certNo}`,
+ txnDate: this.sellDate,
+ amount: payout,
+ particulars: `Premature closure - ${h.party}${this.sellRemark ? ' - ' + this.sellRemark : ''}`
+ },
+ ...this.txns
+ ];
+ this.toast(`Certificate ${h.certNo} closed. Payout ₹${payout.toLocaleString('en-IN')}.`);
+ this.sellCertNo = '';
+ this.sellRemark = '';
+ this.activeTab = 'holdings';
+ }
+
+ // -------------------- Write-off --------------------
+ get writeoffHolding(): InvestmentHolding | undefined {
+ return this.holdings.find((h) => h.certNo === this.writeoffCertNo);
+ }
+
+ saveWriteoff(): void {
+ const h = this.writeoffHolding;
+ if (!h || !this.writeoffReason) {
+ this.toast('Select a certificate and enter a write-off reason.');
+ return;
+ }
+ h.status = 'WRITTEN_OFF';
+ this.txns = [
+ {
+ certNo: h.certNo,
+ instrumentType: 'FD',
+ txnType: 'WRITE_OFF',
+ voucherNo: `INV/WOF/FD-${h.certNo}`,
+ txnDate: this.asOnDate,
+ amount: h.faceValue,
+ particulars: `Written off - ${this.writeoffReason}`
+ },
+ ...this.txns
+ ];
+ this.toast(`Certificate ${h.certNo} written off.`);
+ this.writeoffCertNo = '';
+ this.writeoffReason = '';
+ this.activeTab = 'holdings';
+ }
+
+ // -------------------- Part Payment --------------------
+ get partPayHolding(): InvestmentHolding | undefined {
+ return this.holdings.find((h) => h.certNo === this.partPayCertNo);
+ }
+
+ savePartPayment(): void {
+ const h = this.partPayHolding;
+ if (!h || !this.partPayAmount || this.partPayAmount <= 0 || this.partPayAmount >= h.faceValue) {
+ this.toast('Select a certificate and enter a valid part-payment amount (less than face value).');
+ return;
+ }
+ h.faceValue -= this.partPayAmount;
+ this.txns = [
+ {
+ certNo: h.certNo,
+ instrumentType: 'FD',
+ txnType: 'PART_PAYMENT',
+ voucherNo: `INV/PP/FD-${h.certNo}-${this.txns.length + 1}`,
+ txnDate: this.asOnDate,
+ amount: this.partPayAmount,
+ particulars: `Part payment withdrawn - ${h.party}, balance ₹${h.faceValue.toLocaleString('en-IN')}`
+ },
+ ...this.txns
+ ];
+ this.toast(`Part payment of ₹${this.partPayAmount.toLocaleString('en-IN')} recorded for Certificate ${h.certNo}.`);
+ this.partPayCertNo = '';
+ this.partPayAmount = null;
+ this.activeTab = 'holdings';
+ }
+
+ // -------------------- Helpers --------------------
+ accrued(h: InvestmentHolding): number {
+ return accruedInterest(h, this.asOnDate);
+ }
+
+ maturity(h: InvestmentHolding): number {
+ return maturityAmount(h);
+ }
+
+ daysLeft(h: InvestmentHolding): number {
+ return daysToMaturity(h, this.asOnDate);
+ }
+
+ statusClass(h: InvestmentHolding): string {
+ return `inv-badge inv-badge--${h.status.toLowerCase()}`;
+ }
+
+ private toast(msg: string): void {
+ this.toastMsg = msg;
+ this.toastVisible = true;
+ if (this.toastTimer) {
+ clearTimeout(this.toastTimer);
+ }
+ this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
+ }
+}
diff --git a/src/app/investment/investment-dashboard/investment-dashboard.component.html b/src/app/investment/investment-dashboard/investment-dashboard.component.html
new file mode 100644
index 000000000..43309fba5
--- /dev/null
+++ b/src/app/investment/investment-dashboard/investment-dashboard.component.html
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+ Total Holdings
+ {{ totalHoldings }}
+ {{ activeCount }} active · {{ maturedCount }} matured
+
+
+ Total Invested (Face Value)
+ ₹{{ totalFaceValue | number: '1.2-2' }}
+
+
+ Accrued Interest / Income
+ ₹{{ totalAccruedInterest | number: '1.2-2' }}
+
+
+ Projected Value at Maturity
+ ₹{{ totalMaturityValue | number: '1.2-2' }}
+
+
+
+
+
Instrument-wise Break-up
+
+
+
+ | Instrument Type |
+ Holdings |
+ Face Value |
+ Accrued Interest |
+ Maturity Value |
+ |
+
+
+
+ @for (s of summaries; track s.type) {
+
+ | {{ s.label }} |
+ {{ s.count }} |
+ ₹{{ s.faceValue | number: '1.2-2' }} |
+ ₹{{ s.accruedInterest | number: '1.2-2' }} |
+ ₹{{ s.maturityValue | number: '1.2-2' }} |
+
+ Open
+ |
+
+ }
+
+
+
+ | Total |
+ {{ totalHoldings }} |
+ ₹{{ totalFaceValue | number: '1.2-2' }} |
+ ₹{{ totalAccruedInterest | number: '1.2-2' }} |
+ ₹{{ totalMaturityValue | number: '1.2-2' }} |
+ |
+
+
+
+
+
+
+
Due-Date Diary — Maturing Within 90 Days
+ @if (maturingSoon.length === 0) {
+
No holdings maturing in the next 90 days.
+ } @else {
+
+ @for (h of maturingSoon; track h.certNo) {
+
+
+ {{ h.certNo }}
+ {{ h.scriptName }} — {{ h.party }} ({{ instrumentLabel(h.instrumentType) }})
+
+
+ Matures {{ h.maturityDate }} ·
+ 30"
+ >
+ {{ daysLeft(h) }} days
+
+
+
+ }
+
+ }
+
+
+
+
diff --git a/src/app/investment/investment-dashboard/investment-dashboard.component.ts b/src/app/investment/investment-dashboard/investment-dashboard.component.ts
new file mode 100644
index 000000000..650d5f568
--- /dev/null
+++ b/src/app/investment/investment-dashboard/investment-dashboard.component.ts
@@ -0,0 +1,120 @@
+/**
+ * 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 } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterLink } from '@angular/router';
+import {
+ ALL_HOLDINGS,
+ INSTRUMENT_LABELS,
+ InstrumentType,
+ InvestmentHolding,
+ accruedInterest,
+ daysToMaturity,
+ maturityAmount
+} from '../investment-data';
+
+interface InstrumentSummary {
+ type: InstrumentType;
+ label: string;
+ route: string;
+ count: number;
+ faceValue: number;
+ accruedInterest: number;
+ maturityValue: number;
+}
+
+/** Cross-instrument portfolio dashboard — every figure is derived from ALL_HOLDINGS, never
+ * hand-duplicated, so it can never drift from the per-instrument screens or Reports. */
+@Component({
+ selector: 'mifosx-investment-dashboard',
+ templateUrl: './investment-dashboard.component.html',
+ styleUrls: [
+ '../../cashier/cashier-shared.scss',
+ '../investment-shared.scss'
+ ],
+ standalone: true,
+ imports: [
+ CommonModule,
+ RouterLink
+ ]
+})
+export class InvestmentDashboardComponent {
+ asOnDate = '';
+ private readonly types: Array<{ type: InstrumentType; route: string }> = [
+ { type: 'FD', route: '/investment/fd' },
+ { type: 'GOVT_SECURITY', route: '/investment/bonds' },
+ { type: 'NON_SLR', route: '/investment/bonds' },
+ { type: 'BOND', route: '/investment/bonds' },
+ { type: 'MUTUAL_FUND', route: '/investment/bonds' },
+ { type: 'SHARE', route: '/investment/share' }
+ ];
+
+ constructor() {
+ const d = new Date();
+ this.asOnDate = `${String(d.getDate()).padStart(2, '0')}-${String(d.getMonth() + 1).padStart(2, '0')}-${d.getFullYear()}`;
+ }
+
+ private holdingsOf(type: InstrumentType): InvestmentHolding[] {
+ return ALL_HOLDINGS.filter((h) => h.instrumentType === type);
+ }
+
+ get summaries(): InstrumentSummary[] {
+ return this.types.map(({ type, route }) => {
+ const holdings = this.holdingsOf(type);
+ return {
+ type,
+ label: INSTRUMENT_LABELS[type],
+ route,
+ count: holdings.length,
+ faceValue: holdings.reduce((s, h) => s + h.faceValue, 0),
+ accruedInterest: holdings.reduce((s, h) => s + accruedInterest(h, this.asOnDate), 0),
+ maturityValue: holdings.reduce((s, h) => s + maturityAmount(h), 0)
+ };
+ });
+ }
+
+ get totalHoldings(): number {
+ return ALL_HOLDINGS.length;
+ }
+
+ get totalFaceValue(): number {
+ return ALL_HOLDINGS.reduce((s, h) => s + h.faceValue, 0);
+ }
+
+ get totalAccruedInterest(): number {
+ return ALL_HOLDINGS.reduce((s, h) => s + accruedInterest(h, this.asOnDate), 0);
+ }
+
+ get totalMaturityValue(): number {
+ return ALL_HOLDINGS.reduce((s, h) => s + maturityAmount(h), 0);
+ }
+
+ get activeCount(): number {
+ return ALL_HOLDINGS.filter((h) => h.status === 'ACTIVE').length;
+ }
+
+ get maturedCount(): number {
+ return ALL_HOLDINGS.filter((h) => h.status === 'MATURED').length;
+ }
+
+ /** Holdings maturing within the next 90 days — a "due-date diary" style widget. */
+ get maturingSoon(): InvestmentHolding[] {
+ return ALL_HOLDINGS.filter((h) => h.status === 'ACTIVE' && daysToMaturity(h, this.asOnDate) <= 90).sort(
+ (a, b) => daysToMaturity(a, this.asOnDate) - daysToMaturity(b, this.asOnDate)
+ );
+ }
+
+ daysLeft(h: InvestmentHolding): number {
+ return daysToMaturity(h, this.asOnDate);
+ }
+
+ instrumentLabel(type: InstrumentType): string {
+ return INSTRUMENT_LABELS[type];
+ }
+}
diff --git a/src/app/investment/investment-data.ts b/src/app/investment/investment-data.ts
new file mode 100644
index 000000000..a2cdd253c
--- /dev/null
+++ b/src/app/investment/investment-data.ts
@@ -0,0 +1,1438 @@
+/**
+ * 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/.
+ */
+
+/**
+ * Sample Investment (Treasury) data — dummy static data modelled on a legacy CBS "Investment"
+ * menu (FD / Govt Security / Non-SLR / Bond / Mutual Fund / Share), covering Purchase, Interest,
+ * Sell/Redeem, Write-off and Amortize entries, master data, and reports.
+ *
+ * Maturity amount and accrued interest are always DERIVED from principal/rate/dates via the
+ * helper functions below — never hand-duplicated — so the Dashboard, per-instrument Holdings
+ * lists, and Reports can never drift out of sync with each other.
+ */
+
+export type InstrumentType = 'FD' | 'GOVT_SECURITY' | 'NON_SLR' | 'BOND' | 'MUTUAL_FUND' | 'SHARE';
+export type HoldingStatus = 'ACTIVE' | 'MATURED' | 'SOLD' | 'WRITTEN_OFF';
+export type InterestFreq = 'MONTHLY' | 'QUARTERLY' | 'HALF_YEARLY' | 'YEARLY' | 'CUMULATIVE';
+export type TxnType =
+ | 'PURCHASE'
+ | 'INTEREST'
+ | 'DIVIDEND'
+ | 'SELL'
+ | 'REDEEM'
+ | 'WRITE_OFF'
+ | 'AMORTIZE'
+ | 'PART_PAYMENT';
+
+export const INSTRUMENT_LABELS: Record = {
+ FD: 'Fixed Deposit',
+ GOVT_SECURITY: 'Govt Security',
+ NON_SLR: 'Non-SLR',
+ BOND: 'Bond',
+ MUTUAL_FUND: 'Mutual Fund',
+ SHARE: 'Share'
+};
+
+export interface InvestmentHolding {
+ certNo: string;
+ instrumentType: InstrumentType;
+ scriptCode: string;
+ scriptName: string;
+ party: string;
+ subType: string;
+ faceValue: number;
+ purchaseRate: number;
+ units: number;
+ purchaseDate: string;
+ maturityDate: string;
+ interestRate: number;
+ interestFreq: InterestFreq;
+ glCode: string;
+ glName: string;
+ crToAc: string;
+ brokerCode: string;
+ status: HoldingStatus;
+}
+
+export interface InvestmentTxn {
+ certNo: string;
+ instrumentType: InstrumentType;
+ txnType: TxnType;
+ voucherNo: string;
+ txnDate: string;
+ amount: number;
+ particulars: string;
+}
+
+// -------------------- Date / interest helpers --------------------
+
+export function parseDDMMYYYY(s: string): Date {
+ const [
+ dd,
+ mm,
+ yyyy
+ ] = s.split('-').map(Number);
+ return new Date(yyyy, mm - 1, dd);
+}
+
+export function yearsBetween(d1: string, d2: string): number {
+ const a = parseDDMMYYYY(d1).getTime();
+ const b = parseDDMMYYYY(d2).getTime();
+ return (b - a) / (365.25 * 24 * 3600 * 1000);
+}
+
+/** Maturity amount — compounded annually for cumulative instruments, principal-only otherwise
+ * (non-cumulative interest is paid out periodically instead of being added to the corpus). */
+export function maturityAmount(h: InvestmentHolding): number {
+ const yrs = yearsBetween(h.purchaseDate, h.maturityDate);
+ if (h.interestFreq === 'CUMULATIVE') {
+ return Math.round(h.faceValue * Math.pow(1 + h.interestRate / 100, yrs));
+ }
+ return h.faceValue;
+}
+
+/** Interest accrued from purchase up to `asOnStr`, capped at the maturity date. */
+export function accruedInterest(h: InvestmentHolding, asOnStr: string): number {
+ const cap = Math.min(yearsBetween(h.purchaseDate, asOnStr), yearsBetween(h.purchaseDate, h.maturityDate));
+ const yrs = Math.max(0, cap);
+ return Math.round(h.faceValue * (h.interestRate / 100) * yrs);
+}
+
+export function daysToMaturity(h: InvestmentHolding, asOnStr: string): number {
+ const asOn = parseDDMMYYYY(asOnStr).getTime();
+ const mat = parseDDMMYYYY(h.maturityDate).getTime();
+ return Math.round((mat - asOn) / (24 * 3600 * 1000));
+}
+
+export function formatDDMMYYYY(d: Date): string {
+ return `${String(d.getDate()).padStart(2, '0')}-${String(d.getMonth() + 1).padStart(2, '0')}-${d.getFullYear()}`;
+}
+
+/** Adds a period (years/months/days) to a DD-MM-YYYY date string — used to compute Maturity Date
+ * from an entry form's Deposit/Deal Date + Period Year/Month/Days fields. */
+export function addPeriod(dateStr: string, years: number, months: number, days = 0): string {
+ const d = parseDDMMYYYY(dateStr);
+ d.setFullYear(d.getFullYear() + (years || 0));
+ d.setMonth(d.getMonth() + (months || 0));
+ d.setDate(d.getDate() + (days || 0));
+ return formatDDMMYYYY(d);
+}
+
+export function todayDDMMYYYY(): string {
+ return formatDDMMYYYY(new Date());
+}
+
+// -------------------- Holdings (dummy portfolio) --------------------
+
+export const FD_HOLDINGS: InvestmentHolding[] = [
+ {
+ certNo: '15620',
+ instrumentType: 'FD',
+ scriptCode: 'FD-CUM',
+ scriptName: 'Cumulative Fixed Deposit',
+ party: 'Jila Sahakari Kendriya Bank',
+ subType: 'NON SLR',
+ faceValue: 2401711,
+ purchaseRate: 100,
+ units: 1,
+ purchaseDate: '23-06-2026',
+ maturityDate: '23-12-2028',
+ interestRate: 6.75,
+ interestFreq: 'CUMULATIVE',
+ glCode: '320020',
+ glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: '15621',
+ instrumentType: 'FD',
+ scriptCode: 'FD-CUM',
+ scriptName: 'Cumulative Fixed Deposit',
+ party: 'ABC Cooperative Bank Ltd',
+ subType: 'NON SLR',
+ faceValue: 1500000,
+ purchaseRate: 100,
+ units: 1,
+ purchaseDate: '10-01-2026',
+ maturityDate: '10-01-2029',
+ interestRate: 7.0,
+ interestFreq: 'CUMULATIVE',
+ glCode: '320020',
+ glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: '15622',
+ instrumentType: 'FD',
+ scriptCode: 'FD-CUM',
+ scriptName: 'Cumulative Fixed Deposit',
+ party: 'State Cooperative Bank Ltd',
+ subType: 'NON SLR',
+ faceValue: 5000000,
+ purchaseRate: 100,
+ units: 1,
+ purchaseDate: '05-03-2024',
+ maturityDate: '05-03-2026',
+ interestRate: 6.5,
+ interestFreq: 'YEARLY',
+ glCode: '320020',
+ glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'MATURED'
+ },
+ {
+ certNo: '15623',
+ instrumentType: 'FD',
+ scriptCode: 'FD-MIP',
+ scriptName: 'Monthly Interest Payout FD',
+ party: 'District Central Cooperative Bank',
+ subType: 'NON SLR',
+ faceValue: 1000000,
+ purchaseRate: 100,
+ units: 1,
+ purchaseDate: '15-05-2026',
+ maturityDate: '15-05-2027',
+ interestRate: 7.25,
+ interestFreq: 'MONTHLY',
+ glCode: '320020',
+ glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: '15624',
+ instrumentType: 'FD',
+ scriptCode: 'FD-CUM',
+ scriptName: 'Cumulative Fixed Deposit',
+ party: 'Urban Cooperative Bank Ltd',
+ subType: 'NON SLR',
+ faceValue: 2500000,
+ purchaseRate: 100,
+ units: 1,
+ purchaseDate: '20-02-2024',
+ maturityDate: '20-02-2026',
+ interestRate: 6.9,
+ interestFreq: 'QUARTERLY',
+ glCode: '320020',
+ glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'MATURED'
+ },
+ {
+ certNo: '15625',
+ instrumentType: 'FD',
+ scriptCode: 'FD-CUM',
+ scriptName: 'Cumulative Fixed Deposit',
+ party: 'Nagrik Sahakari Bank Ltd',
+ subType: 'NON SLR',
+ faceValue: 800000,
+ purchaseRate: 100,
+ units: 1,
+ purchaseDate: '01-04-2026',
+ maturityDate: '01-04-2028',
+ interestRate: 7.1,
+ interestFreq: 'HALF_YEARLY',
+ glCode: '320020',
+ glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: '15626',
+ instrumentType: 'FD',
+ scriptCode: 'FD-CUM',
+ scriptName: 'Cumulative Fixed Deposit',
+ party: 'Maharashtra State Cooperative Bank',
+ subType: 'NON SLR',
+ faceValue: 3500000,
+ purchaseRate: 100,
+ units: 1,
+ purchaseDate: '12-07-2025',
+ maturityDate: '12-07-2027',
+ interestRate: 6.6,
+ interestFreq: 'YEARLY',
+ glCode: '320020',
+ glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: '15627',
+ instrumentType: 'FD',
+ scriptCode: 'FD-CUM',
+ scriptName: 'Cumulative Fixed Deposit',
+ party: 'Rajasthan State Cooperative Bank',
+ subType: 'NON SLR',
+ faceValue: 1250000,
+ purchaseRate: 100,
+ units: 1,
+ purchaseDate: '18-06-2026',
+ maturityDate: '18-06-2029',
+ interestRate: 7.0,
+ interestFreq: 'QUARTERLY',
+ glCode: '320020',
+ glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ }
+];
+
+export const GOVT_SECURITY_HOLDINGS: InvestmentHolding[] = [
+ {
+ certNo: 'GSEC-2001',
+ instrumentType: 'GOVT_SECURITY',
+ scriptCode: 'IN0020230012',
+ scriptName: '7.26% GOI 2033',
+ party: 'RBI - SGL',
+ subType: 'SLR',
+ faceValue: 5000000,
+ purchaseRate: 99.5,
+ units: 50000,
+ purchaseDate: '15-01-2026',
+ maturityDate: '15-01-2033',
+ interestRate: 7.26,
+ interestFreq: 'HALF_YEARLY',
+ glCode: '310020',
+ glName: 'INVESTMENT IN GOVT. SECURITIES',
+ crToAc: '310070',
+ brokerCode: 'BRK-002',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'GSEC-2002',
+ instrumentType: 'GOVT_SECURITY',
+ scriptCode: 'IN0020220089',
+ scriptName: '7.10% GOI 2029',
+ party: 'RBI - SGL',
+ subType: 'SLR',
+ faceValue: 3000000,
+ purchaseRate: 100.25,
+ units: 30000,
+ purchaseDate: '10-03-2025',
+ maturityDate: '10-03-2029',
+ interestRate: 7.1,
+ interestFreq: 'HALF_YEARLY',
+ glCode: '310020',
+ glName: 'INVESTMENT IN GOVT. SECURITIES',
+ crToAc: '310070',
+ brokerCode: 'BRK-001',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'GSEC-2003',
+ instrumentType: 'GOVT_SECURITY',
+ scriptCode: 'IN0020210056',
+ scriptName: '6.79% GOI 2027',
+ party: 'RBI - SGL',
+ subType: 'SLR',
+ faceValue: 2000000,
+ purchaseRate: 101.1,
+ units: 20000,
+ purchaseDate: '05-06-2024',
+ maturityDate: '05-06-2027',
+ interestRate: 6.79,
+ interestFreq: 'HALF_YEARLY',
+ glCode: '310020',
+ glName: 'INVESTMENT IN GOVT. SECURITIES',
+ crToAc: '310070',
+ brokerCode: 'BRK-002',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'GSEC-2004',
+ instrumentType: 'GOVT_SECURITY',
+ scriptCode: 'IN0020240034',
+ scriptName: '7.18% GOI 2037',
+ party: 'RBI - SGL',
+ subType: 'SLR',
+ faceValue: 4000000,
+ purchaseRate: 98.75,
+ units: 40000,
+ purchaseDate: '22-04-2026',
+ maturityDate: '22-04-2037',
+ interestRate: 7.18,
+ interestFreq: 'HALF_YEARLY',
+ glCode: '310020',
+ glName: 'INVESTMENT IN GOVT. SECURITIES',
+ crToAc: '310070',
+ brokerCode: 'BRK-001',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'GSEC-2005',
+ instrumentType: 'GOVT_SECURITY',
+ scriptCode: 'TBILL-364D',
+ scriptName: '364 Days Treasury Bill',
+ party: 'RBI - SGL',
+ subType: 'SLR',
+ faceValue: 1500000,
+ purchaseRate: 96.4,
+ units: 15000,
+ purchaseDate: '02-07-2026',
+ maturityDate: '02-07-2027',
+ interestRate: 6.4,
+ interestFreq: 'CUMULATIVE',
+ glCode: '310020',
+ glName: 'INVESTMENT IN GOVT. SECURITIES',
+ crToAc: '310070',
+ brokerCode: 'BRK-003',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'GSEC-2006',
+ instrumentType: 'GOVT_SECURITY',
+ scriptCode: 'MH-SDL-2031',
+ scriptName: '8.05% Maharashtra SDL 2031',
+ party: 'RBI - SGL',
+ subType: 'SLR',
+ faceValue: 2500000,
+ purchaseRate: 100.5,
+ units: 25000,
+ purchaseDate: '15-02-2025',
+ maturityDate: '15-02-2031',
+ interestRate: 8.05,
+ interestFreq: 'HALF_YEARLY',
+ glCode: '310020',
+ glName: 'INVESTMENT IN GOVT. SECURITIES',
+ crToAc: '310070',
+ brokerCode: 'BRK-002',
+ status: 'ACTIVE'
+ }
+];
+
+export const NON_SLR_HOLDINGS: InvestmentHolding[] = [
+ {
+ certNo: 'NSLR-3001',
+ instrumentType: 'NON_SLR',
+ scriptCode: 'REC-7.5-2031',
+ scriptName: 'REC Ltd Bond 7.50% 2031',
+ party: 'REC Limited',
+ subType: 'NON SLR',
+ faceValue: 2000000,
+ purchaseRate: 100,
+ units: 20000,
+ purchaseDate: '11-08-2025',
+ maturityDate: '11-08-2031',
+ interestRate: 7.5,
+ interestFreq: 'YEARLY',
+ glCode: '310030',
+ glName: 'INVESTMENT IN NON SLR SECURITIES',
+ crToAc: '310070',
+ brokerCode: 'BRK-004',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'NSLR-3002',
+ instrumentType: 'NON_SLR',
+ scriptCode: 'NABARD-7.35-2030',
+ scriptName: 'NABARD Bond 7.35% 2030',
+ party: 'NABARD',
+ subType: 'NON SLR',
+ faceValue: 1500000,
+ purchaseRate: 100,
+ units: 15000,
+ purchaseDate: '20-09-2025',
+ maturityDate: '20-09-2030',
+ interestRate: 7.35,
+ interestFreq: 'YEARLY',
+ glCode: '310030',
+ glName: 'INVESTMENT IN NON SLR SECURITIES',
+ crToAc: '310070',
+ brokerCode: 'BRK-004',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'NSLR-3003',
+ instrumentType: 'NON_SLR',
+ scriptCode: 'SIDBI-7.4-2029',
+ scriptName: 'SIDBI Bond 7.40% 2029',
+ party: 'SIDBI',
+ subType: 'NON SLR',
+ faceValue: 1000000,
+ purchaseRate: 100,
+ units: 10000,
+ purchaseDate: '02-11-2025',
+ maturityDate: '02-11-2029',
+ interestRate: 7.4,
+ interestFreq: 'YEARLY',
+ glCode: '310030',
+ glName: 'INVESTMENT IN NON SLR SECURITIES',
+ crToAc: '310070',
+ brokerCode: 'BRK-003',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'NSLR-3004',
+ instrumentType: 'NON_SLR',
+ scriptCode: 'HUDCO-7.6-2032',
+ scriptName: 'HUDCO Bond 7.60% 2032',
+ party: 'HUDCO',
+ subType: 'NON SLR',
+ faceValue: 1800000,
+ purchaseRate: 100,
+ units: 18000,
+ purchaseDate: '14-12-2025',
+ maturityDate: '14-12-2032',
+ interestRate: 7.6,
+ interestFreq: 'YEARLY',
+ glCode: '310030',
+ glName: 'INVESTMENT IN NON SLR SECURITIES',
+ crToAc: '310070',
+ brokerCode: 'BRK-004',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'NSLR-3005',
+ instrumentType: 'NON_SLR',
+ scriptCode: 'PFC-7.45-2028',
+ scriptName: 'PFC Bond 7.45% 2028',
+ party: 'Power Finance Corporation',
+ subType: 'NON SLR',
+ faceValue: 1200000,
+ purchaseRate: 100,
+ units: 12000,
+ purchaseDate: '08-01-2026',
+ maturityDate: '08-01-2028',
+ interestRate: 7.45,
+ interestFreq: 'YEARLY',
+ glCode: '310030',
+ glName: 'INVESTMENT IN NON SLR SECURITIES',
+ crToAc: '310070',
+ brokerCode: 'BRK-003',
+ status: 'ACTIVE'
+ }
+];
+
+export const BOND_HOLDINGS: InvestmentHolding[] = [
+ {
+ certNo: 'BOND-4001',
+ instrumentType: 'BOND',
+ scriptCode: 'LTF-8.1-2028',
+ scriptName: 'L&T Finance NCD 8.10% 2028',
+ party: 'L&T Finance Ltd',
+ subType: 'CORPORATE BOND',
+ faceValue: 1000000,
+ purchaseRate: 100,
+ units: 10000,
+ purchaseDate: '05-02-2026',
+ maturityDate: '05-02-2028',
+ interestRate: 8.1,
+ interestFreq: 'YEARLY',
+ glCode: '310040',
+ glName: 'INVESTMENT IN CORPORATE BONDS',
+ crToAc: '310070',
+ brokerCode: 'BRK-005',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'BOND-4002',
+ instrumentType: 'BOND',
+ scriptCode: 'BAF-7.95-2027',
+ scriptName: 'Bajaj Finance NCD 7.95% 2027',
+ party: 'Bajaj Finance Ltd',
+ subType: 'CORPORATE BOND',
+ faceValue: 1500000,
+ purchaseRate: 100,
+ units: 15000,
+ purchaseDate: '18-03-2026',
+ maturityDate: '18-03-2027',
+ interestRate: 7.95,
+ interestFreq: 'YEARLY',
+ glCode: '310040',
+ glName: 'INVESTMENT IN CORPORATE BONDS',
+ crToAc: '310070',
+ brokerCode: 'BRK-005',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'BOND-4003',
+ instrumentType: 'BOND',
+ scriptCode: 'TCL-8.05-2029',
+ scriptName: 'Tata Capital NCD 8.05% 2029',
+ party: 'Tata Capital Ltd',
+ subType: 'CORPORATE BOND',
+ faceValue: 1200000,
+ purchaseRate: 100,
+ units: 12000,
+ purchaseDate: '25-04-2026',
+ maturityDate: '25-04-2029',
+ interestRate: 8.05,
+ interestFreq: 'YEARLY',
+ glCode: '310040',
+ glName: 'INVESTMENT IN CORPORATE BONDS',
+ crToAc: '310070',
+ brokerCode: 'BRK-006',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'BOND-4004',
+ instrumentType: 'BOND',
+ scriptCode: 'MUT-8.25-2026',
+ scriptName: 'Muthoot Finance NCD 8.25% 2026',
+ party: 'Muthoot Finance Ltd',
+ subType: 'CORPORATE BOND',
+ faceValue: 800000,
+ purchaseRate: 100,
+ units: 8000,
+ purchaseDate: '10-08-2024',
+ maturityDate: '10-08-2026',
+ interestRate: 8.25,
+ interestFreq: 'YEARLY',
+ glCode: '310040',
+ glName: 'INVESTMENT IN CORPORATE BONDS',
+ crToAc: '310070',
+ brokerCode: 'BRK-006',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'BOND-4005',
+ instrumentType: 'BOND',
+ scriptCode: 'STF-8.15-2030',
+ scriptName: 'Shriram Transport NCD 8.15% 2030',
+ party: 'Shriram Transport Finance',
+ subType: 'CORPORATE BOND',
+ faceValue: 2000000,
+ purchaseRate: 100,
+ units: 20000,
+ purchaseDate: '30-05-2026',
+ maturityDate: '30-05-2030',
+ interestRate: 8.15,
+ interestFreq: 'YEARLY',
+ glCode: '310040',
+ glName: 'INVESTMENT IN CORPORATE BONDS',
+ crToAc: '310070',
+ brokerCode: 'BRK-005',
+ status: 'ACTIVE'
+ }
+];
+
+export const MUTUAL_FUND_HOLDINGS: InvestmentHolding[] = [
+ {
+ certNo: 'MF-5001',
+ instrumentType: 'MUTUAL_FUND',
+ scriptCode: 'SBI-OVN',
+ scriptName: 'SBI Overnight Fund',
+ party: 'SBI Mutual Fund',
+ subType: 'DEBT - OVERNIGHT',
+ faceValue: 1000000,
+ purchaseRate: 1200.5,
+ units: 833.33,
+ purchaseDate: '12-01-2026',
+ maturityDate: '12-01-2027',
+ interestRate: 6.8,
+ interestFreq: 'CUMULATIVE',
+ glCode: '310050',
+ glName: 'INVESTMENT IN MUTUAL FUNDS',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'MF-5002',
+ instrumentType: 'MUTUAL_FUND',
+ scriptCode: 'HDFC-LIQ',
+ scriptName: 'HDFC Liquid Fund',
+ party: 'HDFC Mutual Fund',
+ subType: 'DEBT - LIQUID',
+ faceValue: 1500000,
+ purchaseRate: 3800.2,
+ units: 394.72,
+ purchaseDate: '05-02-2026',
+ maturityDate: '05-02-2027',
+ interestRate: 6.9,
+ interestFreq: 'CUMULATIVE',
+ glCode: '310050',
+ glName: 'INVESTMENT IN MUTUAL FUNDS',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'MF-5003',
+ instrumentType: 'MUTUAL_FUND',
+ scriptCode: 'ICICI-MMF',
+ scriptName: 'ICICI Pru Money Market Fund',
+ party: 'ICICI Prudential Mutual Fund',
+ subType: 'DEBT - MONEY MARKET',
+ faceValue: 800000,
+ purchaseRate: 350.75,
+ units: 2280.87,
+ purchaseDate: '18-03-2026',
+ maturityDate: '18-03-2027',
+ interestRate: 7.0,
+ interestFreq: 'CUMULATIVE',
+ glCode: '310050',
+ glName: 'INVESTMENT IN MUTUAL FUNDS',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'MF-5004',
+ instrumentType: 'MUTUAL_FUND',
+ scriptCode: 'AXIS-BPSU',
+ scriptName: 'Axis Banking & PSU Debt Fund',
+ party: 'Axis Mutual Fund',
+ subType: 'DEBT - BANKING & PSU',
+ faceValue: 1200000,
+ purchaseRate: 22.4,
+ units: 53571.43,
+ purchaseDate: '22-04-2026',
+ maturityDate: '22-04-2027',
+ interestRate: 7.2,
+ interestFreq: 'CUMULATIVE',
+ glCode: '310050',
+ glName: 'INVESTMENT IN MUTUAL FUNDS',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'MF-5005',
+ instrumentType: 'MUTUAL_FUND',
+ scriptCode: 'KOTAK-OVN',
+ scriptName: 'Kotak Overnight Fund',
+ party: 'Kotak Mutual Fund',
+ subType: 'DEBT - OVERNIGHT',
+ faceValue: 600000,
+ purchaseRate: 1150.3,
+ units: 521.6,
+ purchaseDate: '02-06-2026',
+ maturityDate: '02-06-2027',
+ interestRate: 6.7,
+ interestFreq: 'CUMULATIVE',
+ glCode: '310050',
+ glName: 'INVESTMENT IN MUTUAL FUNDS',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ }
+];
+
+export const SHARE_HOLDINGS: InvestmentHolding[] = [
+ {
+ certNo: 'SH-6001',
+ instrumentType: 'SHARE',
+ scriptCode: 'SBIN',
+ scriptName: 'State Bank of India',
+ party: 'NSE/BSE',
+ subType: 'EQUITY',
+ faceValue: 375000,
+ purchaseRate: 750,
+ units: 500,
+ purchaseDate: '10-01-2026',
+ maturityDate: '10-01-2036',
+ interestRate: 2.5,
+ interestFreq: 'YEARLY',
+ glCode: '310060',
+ glName: 'INVESTMENT IN SHARES',
+ crToAc: '310070',
+ brokerCode: 'BRK-007',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'SH-6002',
+ instrumentType: 'SHARE',
+ scriptCode: 'HDFCBANK',
+ scriptName: 'HDFC Bank Ltd',
+ party: 'NSE/BSE',
+ subType: 'EQUITY',
+ faceValue: 495000,
+ purchaseRate: 1650,
+ units: 300,
+ purchaseDate: '15-02-2026',
+ maturityDate: '15-02-2036',
+ interestRate: 1.8,
+ interestFreq: 'YEARLY',
+ glCode: '310060',
+ glName: 'INVESTMENT IN SHARES',
+ crToAc: '310070',
+ brokerCode: 'BRK-007',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'SH-6003',
+ instrumentType: 'SHARE',
+ scriptCode: 'NCDC',
+ scriptName: 'National Coop Development Corp',
+ party: 'NCDC',
+ subType: 'CO-OP SHARE',
+ faceValue: 100000,
+ purchaseRate: 100,
+ units: 1000,
+ purchaseDate: '01-04-2025',
+ maturityDate: '01-04-2035',
+ interestRate: 5.0,
+ interestFreq: 'YEARLY',
+ glCode: '310060',
+ glName: 'INVESTMENT IN SHARES',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'SH-6004',
+ instrumentType: 'SHARE',
+ scriptCode: 'DCCB',
+ scriptName: 'District Cooperative Bank Shares',
+ party: 'District Cooperative Bank',
+ subType: 'CO-OP SHARE',
+ faceValue: 100000,
+ purchaseRate: 50,
+ units: 2000,
+ purchaseDate: '12-05-2025',
+ maturityDate: '12-05-2035',
+ interestRate: 5.5,
+ interestFreq: 'YEARLY',
+ glCode: '310060',
+ glName: 'INVESTMENT IN SHARES',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ },
+ {
+ certNo: 'SH-6005',
+ instrumentType: 'SHARE',
+ scriptCode: 'SCF',
+ scriptName: 'State Coop Federation Shares',
+ party: 'State Coop Federation',
+ subType: 'CO-OP SHARE',
+ faceValue: 90000,
+ purchaseRate: 60,
+ units: 1500,
+ purchaseDate: '20-06-2025',
+ maturityDate: '20-06-2035',
+ interestRate: 5.0,
+ interestFreq: 'YEARLY',
+ glCode: '310060',
+ glName: 'INVESTMENT IN SHARES',
+ crToAc: '310070',
+ brokerCode: '',
+ status: 'ACTIVE'
+ }
+];
+
+export const ALL_HOLDINGS: InvestmentHolding[] = [
+ ...FD_HOLDINGS,
+ ...GOVT_SECURITY_HOLDINGS,
+ ...NON_SLR_HOLDINGS,
+ ...BOND_HOLDINGS,
+ ...MUTUAL_FUND_HOLDINGS,
+ ...SHARE_HOLDINGS
+];
+
+// -------------------- Transaction log --------------------
+
+/** A Purchase txn for every holding (so every Holdings-list row has a matching ledger entry),
+ * plus a handful of hand-authored Interest/Sell/Write-off entries for realism. */
+function purchaseTxns(): InvestmentTxn[] {
+ return ALL_HOLDINGS.map((h, i) => ({
+ certNo: h.certNo,
+ instrumentType: h.instrumentType,
+ txnType: 'PURCHASE' as TxnType,
+ voucherNo: `INV/PUR/${1000 + i}`,
+ txnDate: h.purchaseDate,
+ amount: h.faceValue,
+ particulars: `Purchase of ${h.scriptName} - ${h.party}`
+ }));
+}
+
+export const EXTRA_TXNS: InvestmentTxn[] = [
+ {
+ certNo: '15622',
+ instrumentType: 'FD',
+ txnType: 'REDEEM',
+ voucherNo: 'INV/RED/2001',
+ txnDate: '05-03-2026',
+ amount: 5000000 + Math.round(5000000 * 0.065 * 2),
+ particulars: 'Maturity redemption - State Cooperative Bank Ltd FD'
+ },
+ {
+ certNo: '15624',
+ instrumentType: 'FD',
+ txnType: 'REDEEM',
+ voucherNo: 'INV/RED/2002',
+ txnDate: '20-02-2026',
+ amount: 2500000,
+ particulars: 'Maturity redemption (non-cumulative) - Urban Cooperative Bank Ltd FD'
+ },
+ {
+ certNo: 'GSEC-2003',
+ instrumentType: 'GOVT_SECURITY',
+ txnType: 'INTEREST',
+ voucherNo: 'INV/INT/3001',
+ txnDate: '05-06-2026',
+ amount: Math.round(2000000 * 0.0679 * 0.5),
+ particulars: 'Half-yearly coupon - 6.79% GOI 2027'
+ },
+ {
+ certNo: 'GSEC-2001',
+ instrumentType: 'GOVT_SECURITY',
+ txnType: 'INTEREST',
+ voucherNo: 'INV/INT/3002',
+ txnDate: '15-07-2026',
+ amount: Math.round(5000000 * 0.0726 * 0.5),
+ particulars: 'Half-yearly coupon - 7.26% GOI 2033'
+ },
+ {
+ certNo: 'NSLR-3005',
+ instrumentType: 'NON_SLR',
+ txnType: 'INTEREST',
+ voucherNo: 'INV/INT/3003',
+ txnDate: '08-01-2027',
+ amount: Math.round(1200000 * 0.0745),
+ particulars: 'Annual coupon - PFC Bond 7.45% 2028'
+ },
+ {
+ certNo: 'BOND-4004',
+ instrumentType: 'BOND',
+ txnType: 'INTEREST',
+ voucherNo: 'INV/INT/3004',
+ txnDate: '10-08-2025',
+ amount: Math.round(800000 * 0.0825),
+ particulars: 'Annual coupon - Muthoot Finance NCD 8.25% 2026'
+ },
+ {
+ certNo: 'MF-5001',
+ instrumentType: 'MUTUAL_FUND',
+ txnType: 'SELL',
+ voucherNo: 'INV/SEL/4001',
+ txnDate: '20-06-2026',
+ amount: 250000,
+ particulars: 'Partial redemption - SBI Overnight Fund (200 units)'
+ },
+ {
+ certNo: 'SH-6002',
+ instrumentType: 'SHARE',
+ txnType: 'DIVIDEND',
+ voucherNo: 'INV/DIV/5001',
+ txnDate: '25-06-2026',
+ amount: Math.round(300 * 19),
+ particulars: 'Dividend received - HDFC Bank Ltd (Rs 19/share)'
+ },
+ {
+ certNo: 'SH-6001',
+ instrumentType: 'SHARE',
+ txnType: 'DIVIDEND',
+ voucherNo: 'INV/DIV/5002',
+ txnDate: '18-06-2026',
+ amount: Math.round(500 * 13.7),
+ particulars: 'Dividend received - State Bank of India (Rs 13.70/share)'
+ },
+ {
+ certNo: 'BOND-4004',
+ instrumentType: 'BOND',
+ txnType: 'WRITE_OFF',
+ voucherNo: 'INV/WOF/6001',
+ txnDate: '15-07-2026',
+ amount: 0,
+ particulars: 'Reserved - no write-offs booked this year (sample entry)'
+ }
+];
+
+export const ALL_TXNS: InvestmentTxn[] = [
+ ...purchaseTxns(),
+ ...EXTRA_TXNS
+];
+
+// -------------------- Masters --------------------
+
+export interface ScriptMaster {
+ scriptCode: string;
+ scriptName: string;
+ instrumentType: InstrumentType;
+ isin: string;
+ faceValuePerUnit: number;
+}
+
+export const SCRIPT_MASTER: ScriptMaster[] = [
+ { scriptCode: 'FD-CUM', scriptName: 'Cumulative Fixed Deposit', instrumentType: 'FD', isin: '', faceValuePerUnit: 1 },
+ {
+ scriptCode: 'FD-MIP',
+ scriptName: 'Monthly Interest Payout FD',
+ instrumentType: 'FD',
+ isin: '',
+ faceValuePerUnit: 1
+ },
+ {
+ scriptCode: 'IN0020230012',
+ scriptName: '7.26% GOI 2033',
+ instrumentType: 'GOVT_SECURITY',
+ isin: 'IN0020230012',
+ faceValuePerUnit: 100
+ },
+ {
+ scriptCode: 'IN0020220089',
+ scriptName: '7.10% GOI 2029',
+ instrumentType: 'GOVT_SECURITY',
+ isin: 'IN0020220089',
+ faceValuePerUnit: 100
+ },
+ {
+ scriptCode: 'TBILL-364D',
+ scriptName: '364 Days Treasury Bill',
+ instrumentType: 'GOVT_SECURITY',
+ isin: '',
+ faceValuePerUnit: 100
+ },
+ {
+ scriptCode: 'REC-7.5-2031',
+ scriptName: 'REC Ltd Bond 7.50% 2031',
+ instrumentType: 'NON_SLR',
+ isin: 'INE020B08JN0',
+ faceValuePerUnit: 100
+ },
+ {
+ scriptCode: 'NABARD-7.35-2030',
+ scriptName: 'NABARD Bond 7.35% 2030',
+ instrumentType: 'NON_SLR',
+ isin: 'INE261F09EU1',
+ faceValuePerUnit: 100
+ },
+ {
+ scriptCode: 'LTF-8.1-2028',
+ scriptName: 'L&T Finance NCD 8.10% 2028',
+ instrumentType: 'BOND',
+ isin: 'INE027E07BR5',
+ faceValuePerUnit: 100
+ },
+ {
+ scriptCode: 'BAF-7.95-2027',
+ scriptName: 'Bajaj Finance NCD 7.95% 2027',
+ instrumentType: 'BOND',
+ isin: 'INE296A07SP0',
+ faceValuePerUnit: 100
+ },
+ {
+ scriptCode: 'SBI-OVN',
+ scriptName: 'SBI Overnight Fund',
+ instrumentType: 'MUTUAL_FUND',
+ isin: 'INF200KA1Q40',
+ faceValuePerUnit: 1000
+ },
+ {
+ scriptCode: 'HDFC-LIQ',
+ scriptName: 'HDFC Liquid Fund',
+ instrumentType: 'MUTUAL_FUND',
+ isin: 'INF179KA1WR9',
+ faceValuePerUnit: 1000
+ },
+ {
+ scriptCode: 'SBIN',
+ scriptName: 'State Bank of India',
+ instrumentType: 'SHARE',
+ isin: 'INE062A01020',
+ faceValuePerUnit: 1
+ },
+ {
+ scriptCode: 'HDFCBANK',
+ scriptName: 'HDFC Bank Ltd',
+ instrumentType: 'SHARE',
+ isin: 'INE040A01034',
+ faceValuePerUnit: 1
+ }
+];
+
+export interface BrokerMaster {
+ brokerCode: string;
+ brokerName: string;
+ contactNo: string;
+ empanelledOn: string;
+ status: 'ACTIVE' | 'INACTIVE';
+}
+
+export const BROKER_MASTER: BrokerMaster[] = [
+ {
+ brokerCode: 'BRK-001',
+ brokerName: 'ICICI Securities Primary Dealership',
+ contactNo: '022-40001234',
+ empanelledOn: '01-04-2020',
+ status: 'ACTIVE'
+ },
+ {
+ brokerCode: 'BRK-002',
+ brokerName: 'SBI-DFHI Ltd',
+ contactNo: '022-40005678',
+ empanelledOn: '01-04-2020',
+ status: 'ACTIVE'
+ },
+ {
+ brokerCode: 'BRK-003',
+ brokerName: 'STCI Primary Dealer Ltd',
+ contactNo: '022-40009012',
+ empanelledOn: '15-06-2021',
+ status: 'ACTIVE'
+ },
+ {
+ brokerCode: 'BRK-004',
+ brokerName: 'A.K. Capital Services Ltd',
+ contactNo: '022-67543210',
+ empanelledOn: '10-09-2021',
+ status: 'ACTIVE'
+ },
+ {
+ brokerCode: 'BRK-005',
+ brokerName: 'Trust Investment Advisors',
+ contactNo: '022-40103200',
+ empanelledOn: '05-01-2022',
+ status: 'ACTIVE'
+ },
+ {
+ brokerCode: 'BRK-006',
+ brokerName: 'JM Financial Services Ltd',
+ contactNo: '022-66303030',
+ empanelledOn: '12-11-2022',
+ status: 'ACTIVE'
+ },
+ {
+ brokerCode: 'BRK-007',
+ brokerName: 'Zerodha Broking Ltd',
+ contactNo: '080-47184718',
+ empanelledOn: '01-01-2023',
+ status: 'ACTIVE'
+ }
+];
+
+export interface PartyMaster {
+ partyCode: string;
+ partyName: string;
+ partyType: 'BANK' | 'CORPORATE' | 'GOVT' | 'AMC' | 'EXCHANGE' | 'COOP_SOCIETY';
+ panNo: string;
+}
+
+export const PARTY_MASTER: PartyMaster[] = [
+ { partyCode: 'PTY-001', partyName: 'Jila Sahakari Kendriya Bank', partyType: 'BANK', panNo: 'AABCJ1234K' },
+ { partyCode: 'PTY-002', partyName: 'State Cooperative Bank Ltd', partyType: 'BANK', panNo: 'AABCS5678L' },
+ { partyCode: 'PTY-003', partyName: 'RBI - SGL', partyType: 'GOVT', panNo: 'AAACR4849R' },
+ { partyCode: 'PTY-004', partyName: 'REC Limited', partyType: 'CORPORATE', panNo: 'AAACR0641G' },
+ { partyCode: 'PTY-005', partyName: 'NABARD', partyType: 'CORPORATE', panNo: 'AAACN0272B' },
+ { partyCode: 'PTY-006', partyName: 'L&T Finance Ltd', partyType: 'CORPORATE', panNo: 'AAACL0140P' },
+ { partyCode: 'PTY-007', partyName: 'SBI Mutual Fund', partyType: 'AMC', panNo: 'AAATS4325L' },
+ { partyCode: 'PTY-008', partyName: 'HDFC Mutual Fund', partyType: 'AMC', panNo: 'AAATH1234M' },
+ { partyCode: 'PTY-009', partyName: 'NSE/BSE', partyType: 'EXCHANGE', panNo: 'AAACN0028G' },
+ { partyCode: 'PTY-010', partyName: 'District Cooperative Bank', partyType: 'COOP_SOCIETY', panNo: 'AABCD9988K' }
+];
+
+export interface SubTypeMaster {
+ subTypeCode: string;
+ subTypeName: string;
+ instrumentType: InstrumentType;
+}
+
+export const SUBTYPE_MASTER: SubTypeMaster[] = [
+ { subTypeCode: '1', subTypeName: 'SLR', instrumentType: 'GOVT_SECURITY' },
+ { subTypeCode: '2', subTypeName: 'NON SLR', instrumentType: 'NON_SLR' },
+ { subTypeCode: '3', subTypeName: 'CORPORATE BOND', instrumentType: 'BOND' },
+ { subTypeCode: '4', subTypeName: 'EQUITY', instrumentType: 'SHARE' },
+ { subTypeCode: '5', subTypeName: 'CO-OP SHARE', instrumentType: 'SHARE' },
+ { subTypeCode: '6', subTypeName: 'DEBT - OVERNIGHT', instrumentType: 'MUTUAL_FUND' },
+ { subTypeCode: '7', subTypeName: 'DEBT - LIQUID', instrumentType: 'MUTUAL_FUND' }
+];
+
+export interface ControlParameter {
+ paramName: string;
+ paramValue: string;
+ description: string;
+}
+
+export const CONTROL_PARAMETERS: ControlParameter[] = [
+ {
+ paramName: 'SLR_MIN_PERCENT',
+ paramValue: '18.00',
+ description: 'Minimum SLR holding as % of NDTL (RBI requirement)'
+ },
+ {
+ paramName: 'NON_SLR_MAX_PERCENT',
+ paramValue: '10.00',
+ description: 'Maximum Non-SLR exposure as % of total investments'
+ },
+ {
+ paramName: 'FD_AUTO_RENEW_DAYS',
+ paramValue: '7',
+ description: 'Days before maturity to flag FD for auto-renewal review'
+ },
+ {
+ paramName: 'BROKERAGE_CEILING_BPS',
+ paramValue: '25',
+ description: 'Maximum brokerage allowed (basis points) per deal'
+ },
+ {
+ paramName: 'SINGLE_PARTY_EXPOSURE_LIMIT',
+ paramValue: '5000000',
+ description: 'Maximum investment exposure to a single counterparty (Rs)'
+ },
+ {
+ paramName: 'AMORTIZATION_METHOD',
+ paramValue: 'STRAIGHT_LINE',
+ description: 'Default amortization method for premium/discount on bonds'
+ }
+];
+
+export interface MutualFundProductMaster {
+ productCode: string;
+ productName: string;
+ amcName: string;
+ category: string;
+ riskLevel: 'LOW' | 'MODERATE' | 'HIGH';
+}
+
+export const MUTUAL_FUND_PRODUCTS: MutualFundProductMaster[] = [
+ {
+ productCode: 'MFP-01',
+ productName: 'SBI Overnight Fund',
+ amcName: 'SBI Mutual Fund',
+ category: 'Debt - Overnight',
+ riskLevel: 'LOW'
+ },
+ {
+ productCode: 'MFP-02',
+ productName: 'HDFC Liquid Fund',
+ amcName: 'HDFC Mutual Fund',
+ category: 'Debt - Liquid',
+ riskLevel: 'LOW'
+ },
+ {
+ productCode: 'MFP-03',
+ productName: 'ICICI Pru Money Market Fund',
+ amcName: 'ICICI Prudential Mutual Fund',
+ category: 'Debt - Money Market',
+ riskLevel: 'LOW'
+ },
+ {
+ productCode: 'MFP-04',
+ productName: 'Axis Banking & PSU Debt Fund',
+ amcName: 'Axis Mutual Fund',
+ category: 'Debt - Banking & PSU',
+ riskLevel: 'MODERATE'
+ },
+ {
+ productCode: 'MFP-05',
+ productName: 'Kotak Overnight Fund',
+ amcName: 'Kotak Mutual Fund',
+ category: 'Debt - Overnight',
+ riskLevel: 'LOW'
+ }
+];
+
+export interface SglBankMaster {
+ sglCode: string;
+ bankName: string;
+ branch: string;
+}
+
+export const SGL_BANK_MASTER: SglBankMaster[] = [
+ { sglCode: 'SGL-RBI-01', bankName: 'Reserve Bank of India', branch: 'Mumbai (PDO)' },
+ { sglCode: 'SGL-SBI-01', bankName: 'State Bank of India', branch: 'Fort, Mumbai' },
+ { sglCode: 'SGL-HDFC-01', bankName: 'HDFC Bank Ltd', branch: 'Treasury Branch, Mumbai' },
+ { sglCode: 'SGL-CCIL-01', bankName: 'Clearing Corporation of India Ltd', branch: 'Mumbai' }
+];
+
+export interface ScriptMarketDetail {
+ scriptCode: string;
+ marketPrice: number;
+ asOnDate: string;
+ ytm: number;
+}
+
+export const SCRIPT_MARKET_DETAIL: ScriptMarketDetail[] = [
+ { scriptCode: 'IN0020230012', marketPrice: 99.85, asOnDate: '31-07-2026', ytm: 7.31 },
+ { scriptCode: 'IN0020220089', marketPrice: 100.6, asOnDate: '31-07-2026', ytm: 7.02 },
+ { scriptCode: 'IN0020210056', marketPrice: 101.4, asOnDate: '31-07-2026', ytm: 6.71 },
+ { scriptCode: 'IN0020240034', marketPrice: 99.1, asOnDate: '31-07-2026', ytm: 7.24 },
+ { scriptCode: 'REC-7.5-2031', marketPrice: 100.35, asOnDate: '31-07-2026', ytm: 7.44 },
+ { scriptCode: 'LTF-8.1-2028', marketPrice: 100.1, asOnDate: '31-07-2026', ytm: 8.08 }
+];
+
+export interface LienReason {
+ reasonCode: string;
+ reasonDescription: string;
+}
+
+export const LIEN_REASON_MASTER: LienReason[] = [
+ { reasonCode: 'LR-01', reasonDescription: 'Lien marked for loan collateral' },
+ { reasonCode: 'LR-02', reasonDescription: 'Lien marked for court order / litigation' },
+ { reasonCode: 'LR-03', reasonDescription: 'Lien marked for SGL/CSGL margin requirement' },
+ { reasonCode: 'LR-04', reasonDescription: 'Lien marked for guarantee issued' }
+];
+
+/** Daily Script Master (legacy menu "09") — the day's quoted rate per script, entered/imported
+ * every working day. Distinct from Script Market Detail ("13") which also carries YTM/valuation. */
+export interface DailyScriptRate {
+ rateDate: string;
+ scriptCode: string;
+ scriptName: string;
+ openRate: number;
+ closeRate: number;
+ highRate: number;
+ lowRate: number;
+}
+
+export const DAILY_SCRIPT_RATES: DailyScriptRate[] = [
+ {
+ rateDate: '31-07-2026',
+ scriptCode: 'IN0020230012',
+ scriptName: '7.26% GOI 2033',
+ openRate: 99.7,
+ closeRate: 99.85,
+ highRate: 99.95,
+ lowRate: 99.6
+ },
+ {
+ rateDate: '31-07-2026',
+ scriptCode: 'IN0020220089',
+ scriptName: '7.10% GOI 2029',
+ openRate: 100.5,
+ closeRate: 100.6,
+ highRate: 100.7,
+ lowRate: 100.45
+ },
+ {
+ rateDate: '31-07-2026',
+ scriptCode: 'IN0020210056',
+ scriptName: '6.79% GOI 2027',
+ openRate: 101.3,
+ closeRate: 101.4,
+ highRate: 101.5,
+ lowRate: 101.25
+ },
+ {
+ rateDate: '31-07-2026',
+ scriptCode: 'SBIN',
+ scriptName: 'State Bank of India',
+ openRate: 790,
+ closeRate: 802.5,
+ highRate: 808,
+ lowRate: 787
+ },
+ {
+ rateDate: '31-07-2026',
+ scriptCode: 'HDFCBANK',
+ scriptName: 'HDFC Bank Ltd',
+ openRate: 1685,
+ closeRate: 1702.4,
+ highRate: 1710,
+ lowRate: 1680
+ },
+ {
+ rateDate: '30-07-2026',
+ scriptCode: 'IN0020230012',
+ scriptName: '7.26% GOI 2033',
+ openRate: 99.55,
+ closeRate: 99.7,
+ highRate: 99.8,
+ lowRate: 99.5
+ },
+ {
+ rateDate: '30-07-2026',
+ scriptCode: 'SBIN',
+ scriptName: 'State Bank of India',
+ openRate: 782,
+ closeRate: 790,
+ highRate: 794,
+ lowRate: 780
+ }
+];
+
+// -------------------- Dropdown option lists (legacy entry-form selects) --------------------
+
+/** RBI investment classification categories — drive HTM/AFS/HFT valuation treatment. */
+export const CATEGORY_OPTIONS = [
+ 'HTM (Held To Maturity)',
+ 'AFS (Available For Sale)',
+ 'HFT (Held For Trading)'
+];
+
+export const DEAL_TYPE_OPTIONS = [
+ 'OUTRIGHT',
+ 'REPO',
+ 'REVERSE REPO',
+ 'CBLO / TREPS',
+ 'SWITCH'
+];
+
+export const MARKET_OPTIONS = [
+ 'NDS-OM',
+ 'NDS-CALL',
+ 'OTC',
+ 'BSE',
+ 'NSE',
+ 'PRIMARY AUCTION'
+];
+
+export const PAYMENT_MODE_OPTIONS = [
+ 'RTGS',
+ 'NEFT',
+ 'CHEQUE',
+ 'DD / PAY ORDER',
+ 'INTERNAL TRANSFER'
+];
+
+export const DELIVERY_MODE_OPTIONS = [
+ 'SGL',
+ 'CSGL',
+ 'DEMAT',
+ 'PHYSICAL',
+ 'CONSTITUENT SGL'
+];
+
+export const BOND_RATING_OPTIONS = [
+ 'AAA',
+ 'AA+',
+ 'AA',
+ 'AA-',
+ 'A+',
+ 'A',
+ 'A-',
+ 'BBB+',
+ 'BBB',
+ 'SOVEREIGN'
+];
+
+export const INT_TYPE_OPTIONS: Array<{ value: InterestFreq; label: string }> = [
+ { value: 'MONTHLY', label: 'MONTHLY' },
+ { value: 'QUARTERLY', label: 'QUARTERLY' },
+ { value: 'HALF_YEARLY', label: 'HALF YEARLY' },
+ { value: 'YEARLY', label: 'YEARLY' },
+ { value: 'CUMULATIVE', label: 'CUMULATIVE' }
+];
+
+export const AUTHORITY_OPTIONS = [
+ 'Board of Directors',
+ 'Investment Committee',
+ 'Chief Executive Officer',
+ 'General Manager - Treasury',
+ 'Asset Liability Committee (ALCO)'
+];
+
+export const FD_TYPE_OPTIONS = [
+ 'CUMMULATIVE',
+ 'NON-CUMMULATIVE',
+ 'RECURRING',
+ 'CALL DEPOSIT'
+];
+
+/** Approximate YTM for a bond bought at `purchaseRate` per 100 face — coupon adjusted for
+ * the premium/discount amortised over the remaining life. Used to fill the read-only YTM
+ * field on the Bond entry form. */
+export function approxYtm(couponRate: number, purchaseRate: number, yearsToMaturity: number): number {
+ if (!purchaseRate || !yearsToMaturity) {
+ return couponRate;
+ }
+ const annualAmortisation = (100 - purchaseRate) / yearsToMaturity;
+ return +(((couponRate + annualAmortisation) / purchaseRate) * 100).toFixed(2);
+}
diff --git a/src/app/investment/investment-interest/investment-interest.component.html b/src/app/investment/investment-interest/investment-interest.component.html
new file mode 100644
index 000000000..94a3060bf
--- /dev/null
+++ b/src/app/investment/investment-interest/investment-interest.component.html
@@ -0,0 +1,352 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @if (activeTab === 'fdCalc') {
+
+ }
+
+
+ @if (activeTab === 'bondCalc') {
+
+ }
+
+
+ @if (activeTab === 'bondIntRegister') {
+
+
Government Bond Interest Register
+
+
+
+ | Date |
+ Voucher No |
+ Cert No |
+ Instrument |
+ Particulars |
+ Interest Amount |
+
+
+
+ @for (t of bondIntRegister; track t.voucherNo) {
+
+ | {{ t.txnDate }} |
+
+ {{ t.voucherNo }}
+ |
+ {{ t.certNo }} |
+ {{ instrumentLabel(t.instrumentType) }} |
+ {{ t.particulars }} |
+ ₹{{ t.amount | number: '1.2-2' }} |
+
+ } @empty {
+
+ | No bond interest entries recorded yet. |
+
+ }
+
+
+
+ | Total |
+ ₹{{ bondIntRegisterTotal | number: '1.2-2' }} |
+
+
+
+
+ }
+
+
+ @if (activeTab === 'fdIntRegister') {
+
+
FD Interest Register
+
+
+
+ | Date |
+ Voucher No |
+ Cert No |
+ Particulars |
+ Interest Amount |
+
+
+
+ @for (t of fdIntRegister; track t.voucherNo) {
+
+ | {{ t.txnDate }} |
+
+ {{ t.voucherNo }}
+ |
+ {{ t.certNo }} |
+ {{ t.particulars }} |
+ ₹{{ t.amount | number: '1.2-2' }} |
+
+ } @empty {
+
+ | No FD interest entries recorded yet. |
+
+ }
+
+
+
+ | Total |
+ ₹{{ fdIntRegisterTotal | number: '1.2-2' }} |
+
+
+
+
+ }
+
+
+ @if (activeTab === 'fdRegister') {
+
+
FD Register
+
+
+
+ | Cert No |
+ Party |
+ Sub Type |
+ Face Value |
+ Rate |
+ Frequency |
+ Deposit Date |
+ Maturity Date |
+ Days Left |
+ Accrued Interest |
+ Maturity Value |
+ Status |
+
+
+
+ @for (h of fdRegister; track h.certNo) {
+
+ |
+ {{ h.certNo }}
+ |
+ {{ h.party }} |
+ {{ h.subType }} |
+ ₹{{ h.faceValue | number: '1.2-2' }} |
+ {{ h.interestRate }}% |
+ {{ h.interestFreq }} |
+ {{ h.purchaseDate }} |
+ {{ h.maturityDate }} |
+ {{ daysLeft(h) }} |
+ ₹{{ accrued(h) | number: '1.2-2' }} |
+ ₹{{ maturity(h) | number: '1.2-2' }} |
+
+ {{ h.status }}
+ |
+
+ }
+
+
+
+ | Total |
+ ₹{{ fdRegisterFaceTotal | number: '1.2-2' }} |
+ |
+ ₹{{ fdRegisterAccruedTotal | number: '1.2-2' }} |
+ ₹{{ fdRegisterMaturityTotal | number: '1.2-2' }} |
+ |
+
+
+
+
+ }
+
+
+{{ toastMsg }}
diff --git a/src/app/investment/investment-interest/investment-interest.component.ts b/src/app/investment/investment-interest/investment-interest.component.ts
new file mode 100644
index 000000000..8910706cb
--- /dev/null
+++ b/src/app/investment/investment-interest/investment-interest.component.ts
@@ -0,0 +1,247 @@
+/**
+ * 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 } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import {
+ FD_HOLDINGS,
+ GOVT_SECURITY_HOLDINGS,
+ NON_SLR_HOLDINGS,
+ BOND_HOLDINGS,
+ ALL_TXNS,
+ SCRIPT_MASTER,
+ InvestmentHolding,
+ InvestmentTxn,
+ InstrumentType,
+ INSTRUMENT_LABELS,
+ accruedInterest,
+ daysToMaturity,
+ maturityAmount,
+ todayDDMMYYYY
+} from '../investment-data';
+
+type InterestTab = 'fdCalc' | 'bondCalc' | 'bondIntRegister' | 'fdIntRegister' | 'fdRegister';
+
+/**
+ * The legacy "Interest" top-menu for Investment — 5 screens:
+ * 01 FD Interest Calculation, 02 Govt./Bond Int. Calculation (both batch "Process" runs),
+ * 03 Government Bond Int Register, 04 FD Interest Register, 05 FD Register (listings).
+ *
+ * Interest figures are derived from the same holdings used by the Entry screens and Reports,
+ * so a register here always agrees with what a Purchase/Interest entry recorded.
+ */
+@Component({
+ selector: 'mifosx-investment-interest',
+ templateUrl: './investment-interest.component.html',
+ styleUrls: [
+ '../../cashier/cashier-shared.scss',
+ '../investment-shared.scss'
+ ],
+ standalone: true,
+ imports: [
+ CommonModule,
+ FormsModule
+ ]
+})
+export class InvestmentInterestComponent {
+ activeTab: InterestTab = 'fdCalc';
+ asOnDate = todayDDMMYYYY();
+
+ fdHoldings: InvestmentHolding[] = [...FD_HOLDINGS];
+ bondHoldings: InvestmentHolding[] = [
+ ...GOVT_SECURITY_HOLDINGS,
+ ...NON_SLR_HOLDINGS,
+ ...BOND_HOLDINGS
+ ];
+ txns: InvestmentTxn[] = [...ALL_TXNS];
+
+ readonly scriptOptions = SCRIPT_MASTER;
+
+ toastMsg = '';
+ toastVisible = false;
+ private toastTimer: ReturnType | undefined;
+
+ // 01 FD Interest Calculation
+ fdCalcDate = this.asOnDate;
+ fdCalcCertNo = '';
+ fdCalcAll = false;
+
+ // 02 Govt./Bond Int. Calculation
+ bondCalcDate = this.asOnDate;
+ bondCalcType: InstrumentType | '' = '';
+ bondCalcScript = '';
+ bondCalcDealNo = '';
+ bondCalcAll = false;
+
+ // Register filters
+ registerFromDate = '';
+ registerToDate = '';
+
+ setTab(tab: InterestTab): void {
+ this.activeTab = tab;
+ }
+
+ instrumentLabel(type: InstrumentType): string {
+ return INSTRUMENT_LABELS[type];
+ }
+
+ statusClass(h: InvestmentHolding): string {
+ return `inv-badge inv-badge--${h.status.toLowerCase()}`;
+ }
+
+ maturity(h: InvestmentHolding): number {
+ return maturityAmount(h);
+ }
+
+ daysLeft(h: InvestmentHolding): number {
+ return daysToMaturity(h, this.asOnDate);
+ }
+
+ // -------------------- 01 FD Interest Calculation --------------------
+ get fdCalcTargets(): InvestmentHolding[] {
+ if (this.fdCalcAll) {
+ return this.fdHoldings.filter((h) => h.status === 'ACTIVE');
+ }
+ return this.fdHoldings.filter(
+ (h) => h.status === 'ACTIVE' && (!this.fdCalcCertNo || h.certNo === this.fdCalcCertNo)
+ );
+ }
+
+ fdInterest(h: InvestmentHolding): number {
+ return accruedInterest(h, this.fdCalcDate || this.asOnDate);
+ }
+
+ get fdCalcTotal(): number {
+ return this.fdCalcTargets.reduce((s, h) => s + this.fdInterest(h), 0);
+ }
+
+ processFdInterest(): void {
+ const targets = this.fdCalcTargets;
+ if (targets.length === 0) {
+ this.toast('No FD holdings match the selected filters.');
+ return;
+ }
+ const newTxns: InvestmentTxn[] = targets.map((h, i) => ({
+ certNo: h.certNo,
+ instrumentType: 'FD',
+ txnType: 'INTEREST',
+ voucherNo: `INV/FD-INT/${(this.fdCalcDate || this.asOnDate).replace(/-/g, '')}-${i + 1}`,
+ txnDate: this.fdCalcDate || this.asOnDate,
+ amount: this.fdInterest(h),
+ particulars: `FD interest calculated - ${h.party}`
+ }));
+ this.txns = [
+ ...newTxns,
+ ...this.txns
+ ];
+ this.toast(
+ `FD interest processed for ${targets.length} certificate(s). Total ₹${this.fdCalcTotal.toLocaleString('en-IN')}.`
+ );
+ }
+
+ // -------------------- 02 Govt./Bond Int. Calculation --------------------
+ get bondCalcTargets(): InvestmentHolding[] {
+ if (this.bondCalcAll) {
+ return this.bondHoldings.filter((h) => h.status === 'ACTIVE');
+ }
+ return this.bondHoldings.filter((h) => {
+ if (h.status !== 'ACTIVE') return false;
+ if (this.bondCalcType && h.instrumentType !== this.bondCalcType) return false;
+ if (this.bondCalcScript && h.scriptCode !== this.bondCalcScript) return false;
+ if (this.bondCalcDealNo && h.certNo !== this.bondCalcDealNo.trim()) return false;
+ return true;
+ });
+ }
+
+ bondInterest(h: InvestmentHolding): number {
+ return accruedInterest(h, this.bondCalcDate || this.asOnDate);
+ }
+
+ get bondCalcTotal(): number {
+ return this.bondCalcTargets.reduce((s, h) => s + this.bondInterest(h), 0);
+ }
+
+ processBondInterest(): void {
+ const targets = this.bondCalcTargets;
+ if (targets.length === 0) {
+ this.toast('No Govt/Bond holdings match the selected filters.');
+ return;
+ }
+ const newTxns: InvestmentTxn[] = targets.map((h, i) => ({
+ certNo: h.certNo,
+ instrumentType: h.instrumentType,
+ txnType: 'INTEREST',
+ voucherNo: `INV/BOND-INT/${(this.bondCalcDate || this.asOnDate).replace(/-/g, '')}-${i + 1}`,
+ txnDate: this.bondCalcDate || this.asOnDate,
+ amount: this.bondInterest(h),
+ particulars: `Coupon/interest calculated - ${h.scriptName}`
+ }));
+ this.txns = [
+ ...newTxns,
+ ...this.txns
+ ];
+ this.toast(
+ `Interest processed for ${targets.length} holding(s). Total ₹${this.bondCalcTotal.toLocaleString('en-IN')}.`
+ );
+ }
+
+ // -------------------- 03 Government Bond Int Register --------------------
+ get bondIntRegister(): InvestmentTxn[] {
+ return this.txns.filter(
+ (t) => t.txnType === 'INTEREST' && ([
+ 'GOVT_SECURITY',
+ 'NON_SLR',
+ 'BOND'
+ ] as InstrumentType[]).includes(t.instrumentType)
+ );
+ }
+
+ get bondIntRegisterTotal(): number {
+ return this.bondIntRegister.reduce((s, t) => s + t.amount, 0);
+ }
+
+ // -------------------- 04 FD Interest Register --------------------
+ get fdIntRegister(): InvestmentTxn[] {
+ return this.txns.filter((t) => t.txnType === 'INTEREST' && t.instrumentType === 'FD');
+ }
+
+ get fdIntRegisterTotal(): number {
+ return this.fdIntRegister.reduce((s, t) => s + t.amount, 0);
+ }
+
+ // -------------------- 05 FD Register --------------------
+ get fdRegister(): InvestmentHolding[] {
+ return this.fdHoldings;
+ }
+
+ get fdRegisterFaceTotal(): number {
+ return this.fdRegister.reduce((s, h) => s + h.faceValue, 0);
+ }
+
+ get fdRegisterMaturityTotal(): number {
+ return this.fdRegister.reduce((s, h) => s + this.maturity(h), 0);
+ }
+
+ accrued(h: InvestmentHolding): number {
+ return accruedInterest(h, this.asOnDate);
+ }
+
+ get fdRegisterAccruedTotal(): number {
+ return this.fdRegister.reduce((s, h) => s + this.accrued(h), 0);
+ }
+
+ private toast(msg: string): void {
+ this.toastMsg = msg;
+ this.toastVisible = true;
+ if (this.toastTimer) {
+ clearTimeout(this.toastTimer);
+ }
+ this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
+ }
+}
diff --git a/src/app/investment/investment-masters/investment-masters.component.html b/src/app/investment/investment-masters/investment-masters.component.html
new file mode 100644
index 000000000..d44cbf788
--- /dev/null
+++ b/src/app/investment/investment-masters/investment-masters.component.html
@@ -0,0 +1,557 @@
+
+
+
+
+
+
TREASURY · INVESTMENT · MASTERS
+
Investment Masters
+
+ Bank/Party, Script, SubType, Broker, Control Parameter, LIEN Reason, Mutual Fund Product, SGL Bank and Script
+ Market Detail masters.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @if (activeTab === 'party') {
+
+
Add Bank / Party
+
+
+
+
+
+
+
+
+
+
+
+ | Party Code |
+ Party Name |
+ Type |
+ PAN No |
+
+
+
+ @for (p of parties; track p.partyCode) {
+
+ |
+ {{ p.partyCode }}
+ |
+ {{ p.partyName }} |
+ {{ p.partyType }} |
+ {{ p.panNo }} |
+
+ }
+
+
+
+ }
+
+
+ @if (activeTab === 'script') {
+
+ }
+
+
+ @if (activeTab === 'subtype') {
+
+
Add Sub Type
+
+
+
+
+
+
+
+
+
+
+ | Code |
+ Sub Type |
+ Instrument |
+
+
+
+ @for (s of subTypes; track s.subTypeCode + s.subTypeName) {
+
+ |
+ {{ s.subTypeCode }}
+ |
+ {{ s.subTypeName }} |
+ {{ s.instrumentType }} |
+
+ }
+
+
+
+ }
+
+
+ @if (activeTab === 'broker') {
+
+ }
+
+
+ @if (activeTab === 'control') {
+
+ }
+
+
+ @if (activeTab === 'dailyscript') {
+
+ }
+
+
+ @if (activeTab === 'lien') {
+
+
Add LIEN Reason
+
+
+
+
+
+
+
+
+
+ | Reason Code |
+ Description |
+
+
+
+ @for (l of lienReasons; track l.reasonCode) {
+
+ |
+ {{ l.reasonCode }}
+ |
+ {{ l.reasonDescription }} |
+
+ }
+
+
+
+ }
+
+
+ @if (activeTab === 'mfproduct') {
+
+ }
+
+
+ @if (activeTab === 'sglbank') {
+
+
Add SGL Bank
+
+
+
+
+
+
+
+
+
+
+ | SGL Code |
+ Bank Name |
+ Branch |
+
+
+
+ @for (s of sglBanks; track s.sglCode) {
+
+ |
+ {{ s.sglCode }}
+ |
+ {{ s.bankName }} |
+ {{ s.branch }} |
+
+ }
+
+
+
+ }
+
+
+ @if (activeTab === 'marketdetail') {
+
+
Script Market Detail (Daily Script Rates)
+
+
+
+ | Script Code |
+ Market Price |
+ As On |
+ YTM (%) |
+
+
+
+ @for (m of marketDetails; track m.scriptCode) {
+
+ |
+ {{ m.scriptCode }}
+ |
+ ₹{{ m.marketPrice | number: '1.2-2' }} |
+ {{ m.asOnDate }} |
+ {{ m.ytm }}% |
+
+ }
+
+
+
+ }
+
+
+{{ toastMsg }}
diff --git a/src/app/investment/investment-masters/investment-masters.component.ts b/src/app/investment/investment-masters/investment-masters.component.ts
new file mode 100644
index 000000000..de324cc4d
--- /dev/null
+++ b/src/app/investment/investment-masters/investment-masters.component.ts
@@ -0,0 +1,262 @@
+/**
+ * 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 } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import {
+ SCRIPT_MASTER,
+ BROKER_MASTER,
+ PARTY_MASTER,
+ SUBTYPE_MASTER,
+ CONTROL_PARAMETERS,
+ MUTUAL_FUND_PRODUCTS,
+ SGL_BANK_MASTER,
+ SCRIPT_MARKET_DETAIL,
+ DAILY_SCRIPT_RATES,
+ LIEN_REASON_MASTER,
+ ScriptMaster,
+ BrokerMaster,
+ PartyMaster,
+ SubTypeMaster,
+ ControlParameter,
+ MutualFundProductMaster,
+ SglBankMaster,
+ ScriptMarketDetail,
+ DailyScriptRate,
+ LienReason,
+ InstrumentType
+} from '../investment-data';
+
+type MasterTab =
+ | 'party'
+ | 'script'
+ | 'subtype'
+ | 'broker'
+ | 'control'
+ | 'dailyscript'
+ | 'lien'
+ | 'mfproduct'
+ | 'sglbank'
+ | 'marketdetail';
+
+/**
+ * Investment master-data screens (legacy "04-13" Entry submenu masters) — 9 simple dummy
+ * lists, each with an inline Add-row form so the demo stays interactive without a backend.
+ */
+@Component({
+ selector: 'mifosx-investment-masters',
+ templateUrl: './investment-masters.component.html',
+ styleUrls: [
+ '../../cashier/cashier-shared.scss',
+ '../investment-shared.scss'
+ ],
+ standalone: true,
+ imports: [
+ CommonModule,
+ FormsModule
+ ]
+})
+export class InvestmentMastersComponent {
+ activeTab: MasterTab = 'party';
+
+ scripts: ScriptMaster[] = [...SCRIPT_MASTER];
+ brokers: BrokerMaster[] = [...BROKER_MASTER];
+ parties: PartyMaster[] = [...PARTY_MASTER];
+ subTypes: SubTypeMaster[] = [...SUBTYPE_MASTER];
+ controlParams: ControlParameter[] = [...CONTROL_PARAMETERS];
+ mfProducts: MutualFundProductMaster[] = [...MUTUAL_FUND_PRODUCTS];
+ sglBanks: SglBankMaster[] = [...SGL_BANK_MASTER];
+ marketDetails: ScriptMarketDetail[] = [...SCRIPT_MARKET_DETAIL];
+ dailyScriptRates: DailyScriptRate[] = [...DAILY_SCRIPT_RATES];
+ lienReasons: LienReason[] = [...LIEN_REASON_MASTER];
+
+ readonly instrumentTypes: InstrumentType[] = [
+ 'FD',
+ 'GOVT_SECURITY',
+ 'NON_SLR',
+ 'BOND',
+ 'MUTUAL_FUND',
+ 'SHARE'
+ ];
+
+ toastMsg = '';
+ toastVisible = false;
+ private toastTimer: ReturnType | undefined;
+
+ newParty: Partial = {};
+ newScript: Partial = {};
+ newSubType: Partial = {};
+ newBroker: Partial = {};
+ newControlParam: Partial = {};
+ newLienReason: Partial = {};
+ newMfProduct: Partial = {};
+ newSglBank: Partial = {};
+ newDailyRate: Partial = {};
+
+ setTab(tab: MasterTab): void {
+ this.activeTab = tab;
+ }
+
+ addParty(): void {
+ if (!this.newParty.partyName || !this.newParty.partyType) {
+ this.toast('Enter Party Name and Type.');
+ return;
+ }
+ this.parties = [
+ {
+ partyCode: `PTY-${String(this.parties.length + 1).padStart(3, '0')}`,
+ panNo: '',
+ ...this.newParty
+ } as PartyMaster,
+ ...this.parties
+ ];
+ this.newParty = {};
+ this.toast('Party/Bank master added.');
+ }
+
+ addScript(): void {
+ if (!this.newScript.scriptName || !this.newScript.instrumentType) {
+ this.toast('Enter Script Name and Instrument Type.');
+ return;
+ }
+ this.scripts = [
+ {
+ scriptCode: this.newScript.scriptCode || `SCR-${this.scripts.length + 1}`,
+ isin: '',
+ faceValuePerUnit: 100,
+ ...this.newScript
+ } as ScriptMaster,
+ ...this.scripts
+ ];
+ this.newScript = {};
+ this.toast('Script master added.');
+ }
+
+ addSubType(): void {
+ if (!this.newSubType.subTypeName || !this.newSubType.instrumentType) {
+ this.toast('Enter Sub Type Name and Instrument Type.');
+ return;
+ }
+ this.subTypes = [
+ { subTypeCode: String(this.subTypes.length + 1), ...this.newSubType } as SubTypeMaster,
+ ...this.subTypes
+ ];
+ this.newSubType = {};
+ this.toast('Sub Type master added.');
+ }
+
+ addBroker(): void {
+ if (!this.newBroker.brokerName) {
+ this.toast('Enter Broker Name.');
+ return;
+ }
+ this.brokers = [
+ {
+ brokerCode: `BRK-${String(this.brokers.length + 1).padStart(3, '0')}`,
+ contactNo: '',
+ empanelledOn: '',
+ status: 'ACTIVE',
+ ...this.newBroker
+ } as BrokerMaster,
+ ...this.brokers
+ ];
+ this.newBroker = {};
+ this.toast('Broker master added.');
+ }
+
+ addControlParam(): void {
+ if (!this.newControlParam.paramName || !this.newControlParam.paramValue) {
+ this.toast('Enter Parameter Name and Value.');
+ return;
+ }
+ this.controlParams = [
+ { description: '', ...this.newControlParam } as ControlParameter,
+ ...this.controlParams
+ ];
+ this.newControlParam = {};
+ this.toast('Control parameter added.');
+ }
+
+ addLienReason(): void {
+ if (!this.newLienReason.reasonDescription) {
+ this.toast('Enter Reason Description.');
+ return;
+ }
+ this.lienReasons = [
+ { reasonCode: `LR-${String(this.lienReasons.length + 1).padStart(2, '0')}`, ...this.newLienReason } as LienReason,
+ ...this.lienReasons
+ ];
+ this.newLienReason = {};
+ this.toast('LIEN reason added.');
+ }
+
+ addMfProduct(): void {
+ if (!this.newMfProduct.productName || !this.newMfProduct.amcName) {
+ this.toast('Enter Product Name and AMC Name.');
+ return;
+ }
+ this.mfProducts = [
+ {
+ productCode: `MFP-${String(this.mfProducts.length + 1).padStart(2, '0')}`,
+ category: '',
+ riskLevel: 'MODERATE',
+ ...this.newMfProduct
+ } as MutualFundProductMaster,
+ ...this.mfProducts
+ ];
+ this.newMfProduct = {};
+ this.toast('Mutual Fund product added.');
+ }
+
+ addSglBank(): void {
+ if (!this.newSglBank.bankName) {
+ this.toast('Enter Bank Name.');
+ return;
+ }
+ this.sglBanks = [
+ {
+ sglCode: `SGL-${String(this.sglBanks.length + 1).padStart(2, '0')}`,
+ branch: '',
+ ...this.newSglBank
+ } as SglBankMaster,
+ ...this.sglBanks
+ ];
+ this.newSglBank = {};
+ this.toast('SGL Bank master added.');
+ }
+
+ addDailyRate(): void {
+ if (!this.newDailyRate.scriptCode || !this.newDailyRate.rateDate || this.newDailyRate.closeRate == null) {
+ this.toast('Enter Rate Date, Script Code and Close Rate.');
+ return;
+ }
+ const script = this.scripts.find((s) => s.scriptCode === this.newDailyRate.scriptCode);
+ this.dailyScriptRates = [
+ {
+ scriptName: script?.scriptName || '',
+ openRate: this.newDailyRate.closeRate,
+ highRate: this.newDailyRate.closeRate,
+ lowRate: this.newDailyRate.closeRate,
+ ...this.newDailyRate
+ } as DailyScriptRate,
+ ...this.dailyScriptRates
+ ];
+ this.newDailyRate = {};
+ this.toast('Daily script rate added.');
+ }
+
+ private toast(msg: string): void {
+ this.toastMsg = msg;
+ this.toastVisible = true;
+ if (this.toastTimer) {
+ clearTimeout(this.toastTimer);
+ }
+ this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
+ }
+}
diff --git a/src/app/investment/investment-reports/investment-reports.component.html b/src/app/investment/investment-reports/investment-reports.component.html
new file mode 100644
index 000000000..cae27e4a5
--- /dev/null
+++ b/src/app/investment/investment-reports/investment-reports.component.html
@@ -0,0 +1,694 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @if (activeGroup === 'balance') {
+
+
+
+
+
+
+
+ @if (activeReport === 'govtSecBalance' || activeReport === 'shareBalance' || activeReport === 'fdrBalance') {
+
+
+ {{
+ activeReport === 'govtSecBalance'
+ ? 'Govt Security Balance Report'
+ : activeReport === 'shareBalance'
+ ? 'Share Balance Report'
+ : 'FDR Balance Report'
+ }}
+
+
+
+
+ | Cert No |
+ Script / Party |
+ Units |
+ Face Value |
+ Market/Purchase Rate |
+ Status |
+
+
+
+ @for (
+ h of activeReport === 'govtSecBalance'
+ ? govtSecHoldings
+ : activeReport === 'shareBalance'
+ ? shareHoldings
+ : fdHoldings;
+ track h.certNo
+ ) {
+
+ |
+ {{ h.certNo }}
+ |
+ {{ h.scriptName }} — {{ h.party }} |
+ {{ h.units | number }} |
+ ₹{{ h.faceValue | number: '1.2-2' }} |
+ {{ marketPrice(h.scriptCode) || h.purchaseRate }} |
+
+ {{ h.status }}
+ |
+
+ }
+
+
+
+ }
+ @if (activeReport === 'bankInvList') {
+
+
Bank Investment List
+
+
+
+ | Party / Bank |
+ No. of Holdings |
+ Total Face Value |
+
+
+
+ @for (r of bankInvestmentList; track r.party) {
+
+ | {{ r.party }} |
+ {{ r.count }} |
+ ₹{{ r.totalFaceValue | number: '1.2-2' }} |
+
+ }
+
+
+
+ }
+ }
+
+
+ @if (activeGroup === 'ledger') {
+
+
+
+
+
+
+
+
+
+
+
+ @if (
+ activeReport === 'fdLedger' ||
+ activeReport === 'bondLedger' ||
+ activeReport === 'shareLedger' ||
+ activeReport === 'amortizeLedger' ||
+ activeReport === 'dealSlip'
+ ) {
+
+
+ {{
+ activeReport === 'fdLedger'
+ ? 'FD Ledger'
+ : activeReport === 'bondLedger'
+ ? 'Govt/Bond Ledger'
+ : activeReport === 'shareLedger'
+ ? 'Share Ledger'
+ : activeReport === 'amortizeLedger'
+ ? 'Amortize Ledger'
+ : 'Deal Slip Register'
+ }}
+
+
+
+
+ | Date |
+ Voucher No |
+ Cert No |
+ Type |
+ Particulars |
+ Amount |
+
+
+
+ @for (
+ t of activeReport === 'fdLedger'
+ ? fdTxns
+ : activeReport === 'bondLedger'
+ ? bondTxns
+ : activeReport === 'shareLedger'
+ ? shareTxns
+ : activeReport === 'amortizeLedger'
+ ? amortizeTxns
+ : dealSlips;
+ track t.voucherNo
+ ) {
+
+ | {{ t.txnDate }} |
+
+ {{ t.voucherNo }}
+ |
+ {{ t.certNo }} |
+ {{ t.txnType }} |
+ {{ t.particulars }} |
+ ₹{{ t.amount | number: '1.2-2' }} |
+
+ } @empty {
+
+ | No entries. |
+
+ }
+
+
+
+ }
+ @if (activeReport === 'intDueRegister') {
+
+
Interest Due Register (Non-Cumulative Holdings)
+
+
+
+ | Cert No |
+ Script / Party |
+ Frequency |
+ Rate |
+ Due Amount (as on {{ asOnDate }}) |
+
+
+
+ @for (h of interestDueRegister; track h.certNo) {
+
+ |
+ {{ h.certNo }}
+ |
+ {{ h.scriptName }} — {{ h.party }} |
+ {{ h.interestFreq }} |
+ {{ h.interestRate }}% |
+ ₹{{ h.dueAmount | number: '1.2-2' }} |
+
+ }
+
+
+
+ }
+ @if (activeReport === 'slrRegister') {
+
+
SLR Bond Register
+
+
+
+ | Cert No |
+ Script |
+ Face Value |
+ Rate |
+ Maturity Date |
+
+
+
+ @for (h of slrBondRegister; track h.certNo) {
+
+ |
+ {{ h.certNo }}
+ |
+ {{ h.scriptName }} |
+ ₹{{ h.faceValue | number: '1.2-2' }} |
+ {{ h.interestRate }}% |
+ {{ h.maturityDate }} |
+
+ }
+
+
+
+ }
+ @if (activeReport === 'pendingReceived') {
+
+
Received Interest / Dividend
+
+
+
+ | Date |
+ Cert No |
+ Particulars |
+ Amount |
+
+
+
+ @for (t of receivedInterestTxns; track t.voucherNo) {
+
+ | {{ t.txnDate }} |
+ {{ t.certNo }} |
+ {{ t.particulars }} |
+ ₹{{ t.amount | number: '1.2-2' }} |
+
+ }
+
+
+
+
+
Pending Interest (No Interest Posted Yet)
+
+
+
+ | Cert No |
+ Script / Party |
+ Accrued (Pending) |
+
+
+
+ @for (h of pendingInterestHoldings; track h.certNo) {
+
+ |
+ {{ h.certNo }}
+ |
+ {{ h.scriptName }} — {{ h.party }} |
+ ₹{{ accrued(h) | number: '1.2-2' }} |
+
+ }
+
+
+
+ }
+ }
+
+
+ @if (activeGroup === 'maturity') {
+
+
+
+
+
+
+
+
+
+
+
+ @if (activeReport === 'maturedFd') {
+
+
List of Matured FD
+
+
+
+ | Cert No |
+ Party |
+ Face Value |
+ Matured On |
+
+
+
+ @for (h of maturedFdList; track h.certNo) {
+
+ |
+ {{ h.certNo }}
+ |
+ {{ h.party }} |
+ ₹{{ h.faceValue | number: '1.2-2' }} |
+ {{ h.maturityDate }} |
+
+ } @empty {
+
+ | No matured FDs. |
+
+ }
+
+
+
+ }
+ @if (activeReport === 'maturingList') {
+
+
Govt Security / FD Maturity List (sorted by days-to-maturity)
+
+
+
+ | Cert No |
+ Instrument |
+ Script / Party |
+ Maturity Date |
+ Days Left |
+ Maturity Value |
+
+
+
+ @for (h of maturingList; track h.certNo) {
+
+ |
+ {{ h.certNo }}
+ |
+ {{ instrumentLabel(h.instrumentType) }} |
+ {{ h.scriptName }} — {{ h.party }} |
+ {{ h.maturityDate }} |
+ {{ daysLeft(h) }} |
+ ₹{{ maturity(h) | number: '1.2-2' }} |
+
+ }
+
+
+
+ }
+ @if (activeReport === 'maturityPattern') {
+
+
Maturity Pattern Register
+
+
+
+ | Bucket |
+ Holdings |
+ Face Value |
+
+
+
+ @for (b of maturityBuckets; track b.label) {
+
+ | {{ b.label }} |
+ {{ b.count }} |
+ ₹{{ b.faceValue | number: '1.2-2' }} |
+
+ }
+
+
+
+ }
+ @if (
+ activeReport === 'govtSecList' ||
+ activeReport === 'fdSecList' ||
+ activeReport === 'shareSecList' ||
+ activeReport === 'mfList'
+ ) {
+
+
+ {{
+ activeReport === 'govtSecList'
+ ? 'Govt Security List'
+ : activeReport === 'fdSecList'
+ ? 'FD Security List'
+ : activeReport === 'shareSecList'
+ ? 'Share Security List'
+ : 'Mutual Fund List'
+ }}
+
+
+
+
+ | Cert No |
+ Script |
+ Party |
+ Face Value |
+ Rate |
+ Status |
+
+
+
+ @for (
+ h of activeReport === 'govtSecList'
+ ? govtSecList
+ : activeReport === 'fdSecList'
+ ? fdSecList
+ : activeReport === 'shareSecList'
+ ? shareSecList
+ : mutualFundList;
+ track h.certNo
+ ) {
+
+ |
+ {{ h.certNo }}
+ |
+ {{ h.scriptName }} |
+ {{ h.party }} |
+ ₹{{ h.faceValue | number: '1.2-2' }} |
+ {{ h.interestRate }}% |
+
+ {{ h.status }}
+ |
+
+ }
+
+
+
+ }
+ @if (activeReport === 'shiftingReport') {
+
+
+ Shifting Report — SLR Securities with Market Deviation ≥ 1% (AFS/HTM review candidates)
+
+
+
+
+ | Cert No |
+ Script |
+ Purchase Rate |
+ Market Price |
+ Deviation % |
+
+
+
+ @for (h of shiftingCandidates; track h.certNo) {
+
+ |
+ {{ h.certNo }}
+ |
+ {{ h.scriptName }} |
+ {{ h.purchaseRate }} |
+ {{ h.marketPrice }} |
+ 0" [class.col-out]="h.deviationPct < 0">
+ {{ h.deviationPct }}%
+ |
+
+ } @empty {
+
+ | No securities crossing the 1% deviation threshold today. |
+
+ }
+
+
+
+ }
+ }
+
+
+ @if (activeGroup === 'portfolio') {
+
+
Investment Portfolio — Consolidated Summary
+
+
+
+ | Instrument Type |
+ Holdings |
+ Face Value |
+ Accrued Interest |
+ Maturity Value |
+
+
+
+ @for (r of portfolioSummary; track r.label) {
+
+ | {{ r.label }} |
+ {{ r.count }} |
+ ₹{{ r.faceValue | number: '1.2-2' }} |
+ ₹{{ r.accrued | number: '1.2-2' }} |
+ ₹{{ r.maturityValue | number: '1.2-2' }} |
+
+ }
+
+
+
+ | Total |
+ |
+ ₹{{ portfolioTotalFaceValue | number: '1.2-2' }} |
+ ₹{{ portfolioTotalAccrued | number: '1.2-2' }} |
+ ₹{{ portfolioTotalMaturity | number: '1.2-2' }} |
+
+
+
+
+ }
+
diff --git a/src/app/investment/investment-reports/investment-reports.component.ts b/src/app/investment/investment-reports/investment-reports.component.ts
new file mode 100644
index 000000000..551a5423a
--- /dev/null
+++ b/src/app/investment/investment-reports/investment-reports.component.ts
@@ -0,0 +1,247 @@
+/**
+ * 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 } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import {
+ ALL_HOLDINGS,
+ ALL_TXNS,
+ FD_HOLDINGS,
+ GOVT_SECURITY_HOLDINGS,
+ NON_SLR_HOLDINGS,
+ BOND_HOLDINGS,
+ MUTUAL_FUND_HOLDINGS,
+ SHARE_HOLDINGS,
+ SCRIPT_MARKET_DETAIL,
+ InstrumentType,
+ InvestmentHolding,
+ InvestmentTxn,
+ INSTRUMENT_LABELS,
+ accruedInterest,
+ maturityAmount,
+ daysToMaturity,
+ todayDDMMYYYY
+} from '../investment-data';
+
+type ReportGroup = 'balance' | 'ledger' | 'maturity' | 'portfolio';
+
+/**
+ * ~20 legacy Investment reports, grouped into 4 tabs. Every figure here is derived from the
+ * same ALL_HOLDINGS/ALL_TXNS arrays used by the Entry screens and Dashboard — so a report can
+ * never drift out of sync with what a Purchase/Sell/Interest entry actually recorded.
+ */
+@Component({
+ selector: 'mifosx-investment-reports',
+ templateUrl: './investment-reports.component.html',
+ styleUrls: [
+ '../../cashier/cashier-shared.scss',
+ '../investment-shared.scss'
+ ],
+ standalone: true,
+ imports: [CommonModule]
+})
+export class InvestmentReportsComponent {
+ activeGroup: ReportGroup = 'balance';
+ activeReport = 'govtSecBalance';
+ asOnDate = todayDDMMYYYY();
+
+ setGroup(group: ReportGroup, defaultReport: string): void {
+ this.activeGroup = group;
+ this.activeReport = defaultReport;
+ }
+
+ setReport(report: string): void {
+ this.activeReport = report;
+ }
+
+ instrumentLabel(type: InstrumentType): string {
+ return INSTRUMENT_LABELS[type];
+ }
+
+ accrued(h: InvestmentHolding): number {
+ return accruedInterest(h, this.asOnDate);
+ }
+
+ maturity(h: InvestmentHolding): number {
+ return maturityAmount(h);
+ }
+
+ daysLeft(h: InvestmentHolding): number {
+ return daysToMaturity(h, this.asOnDate);
+ }
+
+ statusClass(h: InvestmentHolding): string {
+ return `inv-badge inv-badge--${h.status.toLowerCase()}`;
+ }
+
+ marketPrice(scriptCode: string): number | null {
+ return SCRIPT_MARKET_DETAIL.find((m) => m.scriptCode === scriptCode)?.marketPrice ?? null;
+ }
+
+ // -------------------- Balance Reports --------------------
+ get govtSecHoldings(): InvestmentHolding[] {
+ return GOVT_SECURITY_HOLDINGS;
+ }
+ get shareHoldings(): InvestmentHolding[] {
+ return SHARE_HOLDINGS;
+ }
+ get fdHoldings(): InvestmentHolding[] {
+ return FD_HOLDINGS;
+ }
+ get bankInvestmentList(): Array<{ party: string; count: number; totalFaceValue: number }> {
+ const map = new Map();
+ for (const h of ALL_HOLDINGS) {
+ const row = map.get(h.party) || { party: h.party, count: 0, totalFaceValue: 0 };
+ row.count += 1;
+ row.totalFaceValue += h.faceValue;
+ map.set(h.party, row);
+ }
+ return [...map.values()].sort((a, b) => b.totalFaceValue - a.totalFaceValue);
+ }
+
+ // -------------------- Ledgers & Registers --------------------
+ get fdTxns(): InvestmentTxn[] {
+ return ALL_TXNS.filter((t) => t.instrumentType === 'FD');
+ }
+ get bondTxns(): InvestmentTxn[] {
+ return ALL_TXNS.filter((t) => ([
+ 'GOVT_SECURITY',
+ 'NON_SLR',
+ 'BOND'
+ ] as InstrumentType[]).includes(t.instrumentType));
+ }
+ get shareTxns(): InvestmentTxn[] {
+ return ALL_TXNS.filter((t) => t.instrumentType === 'SHARE');
+ }
+ get amortizeTxns(): InvestmentTxn[] {
+ return ALL_TXNS.filter((t) => t.txnType === 'AMORTIZE');
+ }
+ get dealSlips(): InvestmentTxn[] {
+ return ALL_TXNS.filter((t) => t.txnType === 'PURCHASE');
+ }
+ get slrBondRegister(): InvestmentHolding[] {
+ return GOVT_SECURITY_HOLDINGS.filter((h) => h.subType === 'SLR');
+ }
+ get interestDueRegister(): Array {
+ return ALL_HOLDINGS.filter((h) => h.status === 'ACTIVE' && h.interestFreq !== 'CUMULATIVE').map((h) => ({
+ ...h,
+ dueAmount: this.accrued(h)
+ }));
+ }
+ get receivedInterestTxns(): InvestmentTxn[] {
+ return ALL_TXNS.filter((t) => t.txnType === 'INTEREST' || t.txnType === 'DIVIDEND');
+ }
+ get pendingInterestHoldings(): InvestmentHolding[] {
+ const receivedCerts = new Set(this.receivedInterestTxns.map((t) => t.certNo));
+ return ALL_HOLDINGS.filter(
+ (h) => h.status === 'ACTIVE' && h.interestFreq !== 'CUMULATIVE' && !receivedCerts.has(h.certNo)
+ );
+ }
+
+ // -------------------- Maturity & Lists --------------------
+ get maturedFdList(): InvestmentHolding[] {
+ return FD_HOLDINGS.filter((h) => h.status === 'MATURED');
+ }
+ get maturingList(): InvestmentHolding[] {
+ return [
+ ...FD_HOLDINGS,
+ ...GOVT_SECURITY_HOLDINGS
+ ]
+ .filter((h) => h.status === 'ACTIVE')
+ .sort((a, b) => this.daysLeft(a) - this.daysLeft(b));
+ }
+ get maturityBuckets(): Array<{ label: string; count: number; faceValue: number }> {
+ const buckets = [
+ { label: '0-1 Year', min: -99999, max: 365, count: 0, faceValue: 0 },
+ { label: '1-3 Years', min: 366, max: 1095, count: 0, faceValue: 0 },
+ { label: '3-5 Years', min: 1096, max: 1825, count: 0, faceValue: 0 },
+ { label: 'Above 5 Years', min: 1826, max: 999999, count: 0, faceValue: 0 }
+ ];
+ for (const h of ALL_HOLDINGS.filter((x) => x.status === 'ACTIVE')) {
+ const days = this.daysLeft(h);
+ const bucket = buckets.find((b) => days >= b.min && days <= b.max);
+ if (bucket) {
+ bucket.count += 1;
+ bucket.faceValue += h.faceValue;
+ }
+ }
+ return buckets;
+ }
+ get govtSecList(): InvestmentHolding[] {
+ return GOVT_SECURITY_HOLDINGS;
+ }
+ get fdSecList(): InvestmentHolding[] {
+ return FD_HOLDINGS;
+ }
+ get shareSecList(): InvestmentHolding[] {
+ return SHARE_HOLDINGS;
+ }
+ get mutualFundList(): InvestmentHolding[] {
+ return MUTUAL_FUND_HOLDINGS;
+ }
+ get nonSlrList(): InvestmentHolding[] {
+ return NON_SLR_HOLDINGS;
+ }
+ get bondList(): InvestmentHolding[] {
+ return BOND_HOLDINGS;
+ }
+ /** Shifting Report — SLR securities whose market value has moved away from book value by
+ * more than 1%, a proxy for "category shift" candidates (AFS/HTM reclassification review). */
+ get shiftingCandidates(): Array {
+ return GOVT_SECURITY_HOLDINGS.map((h) => {
+ const mp = this.marketPrice(h.scriptCode);
+ if (mp === null) {
+ return null;
+ }
+ const deviationPct = +(((mp - h.purchaseRate) / h.purchaseRate) * 100).toFixed(2);
+ return { ...h, marketPrice: mp, deviationPct };
+ })
+ .filter(
+ (x): x is InvestmentHolding & { marketPrice: number; deviationPct: number } =>
+ x !== null && Math.abs(x.deviationPct) >= 1
+ )
+ .sort((a, b) => Math.abs(b.deviationPct) - Math.abs(a.deviationPct));
+ }
+
+ // -------------------- Portfolio --------------------
+ private readonly portfolioTypes: InstrumentType[] = [
+ 'FD',
+ 'GOVT_SECURITY',
+ 'NON_SLR',
+ 'BOND',
+ 'MUTUAL_FUND',
+ 'SHARE'
+ ];
+ get portfolioSummary(): Array<{
+ label: string;
+ count: number;
+ faceValue: number;
+ accrued: number;
+ maturityValue: number;
+ }> {
+ return this.portfolioTypes.map((type) => {
+ const holdings = ALL_HOLDINGS.filter((h) => h.instrumentType === type);
+ return {
+ label: INSTRUMENT_LABELS[type],
+ count: holdings.length,
+ faceValue: holdings.reduce((s, h) => s + h.faceValue, 0),
+ accrued: holdings.reduce((s, h) => s + this.accrued(h), 0),
+ maturityValue: holdings.reduce((s, h) => s + this.maturity(h), 0)
+ };
+ });
+ }
+ get portfolioTotalFaceValue(): number {
+ return this.portfolioSummary.reduce((s, r) => s + r.faceValue, 0);
+ }
+ get portfolioTotalAccrued(): number {
+ return this.portfolioSummary.reduce((s, r) => s + r.accrued, 0);
+ }
+ get portfolioTotalMaturity(): number {
+ return this.portfolioSummary.reduce((s, r) => s + r.maturityValue, 0);
+ }
+}
diff --git a/src/app/investment/investment-routing.module.ts b/src/app/investment/investment-routing.module.ts
new file mode 100644
index 000000000..bfe085efb
--- /dev/null
+++ b/src/app/investment/investment-routing.module.ts
@@ -0,0 +1,71 @@
+/**
+ * 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 { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { Route } from '../core/route/route.service';
+import { InvestmentDashboardComponent } from './investment-dashboard/investment-dashboard.component';
+import { FdInvestmentComponent } from './fd-investment/fd-investment.component';
+import { BondInvestmentComponent } from './bond-investment/bond-investment.component';
+import { ShareInvestmentComponent } from './share-investment/share-investment.component';
+import { InvestmentMastersComponent } from './investment-masters/investment-masters.component';
+import { InvestmentReportsComponent } from './investment-reports/investment-reports.component';
+import { InvestmentInterestComponent } from './investment-interest/investment-interest.component';
+
+const routes: Routes = [
+ Route.withShell([
+ {
+ path: 'investment',
+ data: { title: 'Investment', breadcrumb: 'Investment' },
+ children: [
+ {
+ path: 'dashboard',
+ component: InvestmentDashboardComponent,
+ data: { title: 'Investment Dashboard', breadcrumb: 'Investment Dashboard' }
+ },
+ {
+ path: 'fd',
+ component: FdInvestmentComponent,
+ data: { title: 'FD Investment', breadcrumb: 'FD Investment' }
+ },
+ {
+ path: 'bonds',
+ component: BondInvestmentComponent,
+ data: { title: 'Govt Security / Bond Investment', breadcrumb: 'Govt Security / Bond Investment' }
+ },
+ {
+ path: 'share',
+ component: ShareInvestmentComponent,
+ data: { title: 'Share Investment', breadcrumb: 'Share Investment' }
+ },
+ {
+ path: 'masters',
+ component: InvestmentMastersComponent,
+ data: { title: 'Investment Masters', breadcrumb: 'Investment Masters' }
+ },
+ {
+ path: 'interest',
+ component: InvestmentInterestComponent,
+ data: { title: 'Investment Interest', breadcrumb: 'Investment Interest' }
+ },
+ {
+ path: 'reports',
+ component: InvestmentReportsComponent,
+ data: { title: 'Investment Reports', breadcrumb: 'Investment Reports' }
+ }
+ ]
+ }
+ ])
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class InvestmentRoutingModule {}
diff --git a/src/app/investment/investment-shared.scss b/src/app/investment/investment-shared.scss
new file mode 100644
index 000000000..57a554a2e
--- /dev/null
+++ b/src/app/investment/investment-shared.scss
@@ -0,0 +1,225 @@
+/**
+ * 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/.
+ */
+
+// Extra styles layered on top of ../cashier/cashier-shared.scss (pg-*, cctr-*) for the
+// Investment module — instrument nav cards, status badges, KPI tiles, entry-form layout.
+
+.inv-kpis {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 12px;
+
+ @media (width <= 1100px) {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+.inv-kpi {
+ background: #fff;
+ border: 1px solid #e2e8f0;
+ border-radius: 12px;
+ padding: 14px 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ &__label {
+ font-size: 10px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.07em;
+ color: #64748b;
+ }
+
+ &__value {
+ font-size: 20px;
+ font-weight: 800;
+ font-variant-numeric: tabular-nums;
+ color: #0f172a;
+ }
+
+ &__sub {
+ font-size: 11px;
+ color: #94a3b8;
+ }
+}
+
+.inv-nav-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+ gap: 12px;
+}
+
+.inv-nav-card {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ padding: 16px;
+ background: #fff;
+ border: 1px solid #e2e8f0;
+ border-radius: 12px;
+ text-decoration: none;
+ color: inherit;
+ transition:
+ border-color 0.15s,
+ box-shadow 0.15s;
+
+ &:hover {
+ border-color: #0f4f3c;
+ box-shadow: 0 2px 10px rgb(15 79 60 / 10%);
+ }
+
+ &__label {
+ font-size: 10px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: #64748b;
+ }
+
+ &__count {
+ font-size: 22px;
+ font-weight: 800;
+ color: #0f172a;
+ }
+
+ &__value {
+ font-size: 13px;
+ font-weight: 600;
+ color: #0f4f3c;
+ }
+}
+
+.inv-badge {
+ display: inline-flex;
+ align-items: center;
+ padding: 3px 10px;
+ border-radius: 999px;
+ font-size: 11px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+
+ &--active {
+ background: #f0fdf4;
+ color: #15803d;
+ border: 1px solid #bbf7d0;
+ }
+
+ &--matured {
+ background: #eff6ff;
+ color: #1d4ed8;
+ border: 1px solid #bfdbfe;
+ }
+
+ &--sold {
+ background: #fefce8;
+ color: #a16207;
+ border: 1px solid #fde68a;
+ }
+
+ &--written_off {
+ background: #fef2f2;
+ color: #b91c1c;
+ border: 1px solid #fecaca;
+ }
+}
+
+.inv-form-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+ gap: 14px 20px;
+ padding: 18px;
+}
+
+.inv-form-field {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ font-size: 11px;
+ font-weight: 600;
+ color: #64748b;
+
+ input,
+ select {
+ height: 36px;
+ padding: 0 10px;
+ border: 1px solid #e2e8f0;
+ border-radius: 8px;
+ font-size: 13px;
+ color: #0d1b2a;
+ font-family: inherit;
+ background: #fff;
+ }
+
+ // Computed / lookup-filled fields are rendered read-only and greyed out so they read as
+ // derived values rather than something the user is expected to type into.
+ /* stylelint-disable-next-line selector-no-qualifying-type -- [readonly] only applies to inputs */
+ input[readonly] {
+ background: #f8fafc;
+ color: #64748b;
+ }
+}
+
+.inv-form-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ padding: 0 18px 18px;
+}
+
+.inv-maturity-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px 14px;
+ border-bottom: 1px dotted #eef2f7;
+ font-size: 13px;
+
+ &:last-child {
+ border-bottom: none;
+ }
+}
+
+.inv-maturity-days {
+ font-weight: 800;
+ font-variant-numeric: tabular-nums;
+
+ &--soon {
+ color: #dc2626;
+ }
+
+ &--ok {
+ color: #16a34a;
+ }
+}
+
+.inv-toast {
+ position: fixed;
+ bottom: 24px;
+ left: 50%;
+ transform: translateX(-50%) translateY(20px);
+ background: #0f172a;
+ color: #fff;
+ padding: 12px 20px;
+ border-radius: 10px;
+ font-size: 13px;
+ font-weight: 600;
+ box-shadow: 0 8px 24px rgb(0 0 0 / 25%);
+ opacity: 0;
+ pointer-events: none;
+ transition:
+ opacity 0.2s,
+ transform 0.2s;
+ z-index: 2000;
+
+ &.show {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+ }
+}
diff --git a/src/app/investment/investment.module.ts b/src/app/investment/investment.module.ts
new file mode 100644
index 000000000..a60f44544
--- /dev/null
+++ b/src/app/investment/investment.module.ts
@@ -0,0 +1,34 @@
+/**
+ * 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 { NgModule } from '@angular/core';
+
+import { SharedModule } from '../shared/shared.module';
+import { InvestmentRoutingModule } from './investment-routing.module';
+import { InvestmentDashboardComponent } from './investment-dashboard/investment-dashboard.component';
+import { FdInvestmentComponent } from './fd-investment/fd-investment.component';
+import { BondInvestmentComponent } from './bond-investment/bond-investment.component';
+import { ShareInvestmentComponent } from './share-investment/share-investment.component';
+import { InvestmentMastersComponent } from './investment-masters/investment-masters.component';
+import { InvestmentReportsComponent } from './investment-reports/investment-reports.component';
+import { InvestmentInterestComponent } from './investment-interest/investment-interest.component';
+
+@NgModule({
+ imports: [
+ SharedModule,
+ InvestmentRoutingModule,
+ InvestmentDashboardComponent,
+ FdInvestmentComponent,
+ BondInvestmentComponent,
+ ShareInvestmentComponent,
+ InvestmentMastersComponent,
+ InvestmentReportsComponent,
+ InvestmentInterestComponent
+ ]
+})
+export class InvestmentModule {}
diff --git a/src/app/investment/share-investment/share-investment.component.html b/src/app/investment/share-investment/share-investment.component.html
new file mode 100644
index 000000000..7312ec74c
--- /dev/null
+++ b/src/app/investment/share-investment/share-investment.component.html
@@ -0,0 +1,276 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @if (activeTab === 'holdings') {
+
+
Share Holdings Register
+
+
+
+ | Cert No |
+ Script |
+ Party |
+ Units |
+ Rate |
+ Face Value |
+ Purchase Date |
+ Status |
+
+
+
+ @for (h of holdings; track h.certNo) {
+
+ |
+ {{ h.certNo }}
+ |
+ {{ h.scriptName }} |
+ {{ h.party }} |
+ {{ h.units | number }} |
+ ₹{{ h.purchaseRate | number: '1.2-2' }} |
+ ₹{{ h.faceValue | number: '1.2-2' }} |
+ {{ h.purchaseDate }} |
+
+ {{ h.status }}
+ |
+
+ }
+
+
+
+ }
+
+
+ @if (activeTab === 'purchase') {
+
+ }
+
+
+ @if (activeTab === 'dividend') {
+
+ }
+
+
+ @if (activeTab === 'sell') {
+
+ }
+
+
+ @if (activeTab === 'writeoff') {
+
+
Investment - Share Write Off Entry
+
+
+ @if (writeoffHolding; as h) {
+
+ }
+
+
+
+
+
+
+ }
+
+ @if (activeTab !== 'holdings') {
+
+
Share Transaction Log
+
+
+
+ | Date |
+ Voucher No |
+ Cert No |
+ Type |
+ Particulars |
+ Amount |
+
+
+
+ @for (t of txns; track t.voucherNo) {
+
+ | {{ t.txnDate }} |
+
+ {{ t.voucherNo }}
+ |
+ {{ t.certNo }} |
+ {{ t.txnType }} |
+ {{ t.particulars }} |
+ ₹{{ t.amount | number: '1.2-2' }} |
+
+ }
+
+
+
+ }
+
+
+{{ toastMsg }}
diff --git a/src/app/investment/share-investment/share-investment.component.ts b/src/app/investment/share-investment/share-investment.component.ts
new file mode 100644
index 000000000..f5835ee0d
--- /dev/null
+++ b/src/app/investment/share-investment/share-investment.component.ts
@@ -0,0 +1,263 @@
+/**
+ * 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 } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { SHARE_HOLDINGS, ALL_TXNS, InvestmentHolding, InvestmentTxn, todayDDMMYYYY } from '../investment-data';
+
+type ShareTab = 'holdings' | 'purchase' | 'dividend' | 'sell' | 'writeoff';
+
+interface SharePurchaseForm {
+ certNo: string;
+ scriptCode: string;
+ scriptName: string;
+ party: string;
+ subType: string;
+ units: number | null;
+ purchaseRate: number | null;
+ purchaseDate: string;
+ brokerCode: string;
+ crToAc: string;
+}
+
+/**
+ * Share Entry treasury screen — Holdings register + Purchase / Dividend / Sell / Write-off
+ * entry tabs (legacy "02 Share Entry" menu). Dummy in-memory data; Save appends locally.
+ */
+@Component({
+ selector: 'mifosx-share-investment',
+ templateUrl: './share-investment.component.html',
+ styleUrls: [
+ '../../cashier/cashier-shared.scss',
+ '../investment-shared.scss'
+ ],
+ standalone: true,
+ imports: [
+ CommonModule,
+ FormsModule
+ ]
+})
+export class ShareInvestmentComponent {
+ activeTab: ShareTab = 'holdings';
+ asOnDate = todayDDMMYYYY();
+
+ holdings: InvestmentHolding[] = [...SHARE_HOLDINGS];
+ txns: InvestmentTxn[] = ALL_TXNS.filter((t) => t.instrumentType === 'SHARE');
+
+ toastMsg = '';
+ toastVisible = false;
+ private toastTimer: ReturnType | undefined;
+
+ purchaseForm: SharePurchaseForm = this.blankPurchaseForm();
+ dividendCertNo = '';
+ dividendPerShare: number | null = null;
+ sellCertNo = '';
+ sellUnits: number | null = null;
+ sellRate: number | null = null;
+ writeoffCertNo = '';
+ writeoffReason = '';
+
+ setTab(tab: ShareTab): void {
+ this.activeTab = tab;
+ }
+
+ get activeHoldings(): InvestmentHolding[] {
+ return this.holdings.filter((h) => h.status === 'ACTIVE');
+ }
+
+ private blankPurchaseForm(): SharePurchaseForm {
+ return {
+ certNo: `SH-${6000 + this.holdings.length + 1}`,
+ scriptCode: '',
+ scriptName: '',
+ party: 'NSE/BSE',
+ subType: 'EQUITY',
+ units: null,
+ purchaseRate: null,
+ purchaseDate: this.asOnDate,
+ brokerCode: '',
+ crToAc: '310070'
+ };
+ }
+
+ savePurchase(): void {
+ const f = this.purchaseForm;
+ if (!f.scriptName || !f.units || !f.purchaseRate) {
+ this.toast('Please fill Script Name, No. of Units and Purchase Rate.');
+ return;
+ }
+ const faceValue = f.units * f.purchaseRate;
+ const holding: InvestmentHolding = {
+ certNo: f.certNo,
+ instrumentType: 'SHARE',
+ scriptCode: f.scriptCode || f.scriptName,
+ scriptName: f.scriptName,
+ party: f.party,
+ subType: f.subType,
+ faceValue,
+ purchaseRate: f.purchaseRate,
+ units: f.units,
+ purchaseDate: f.purchaseDate,
+ maturityDate: f.purchaseDate,
+ interestRate: 0,
+ interestFreq: 'YEARLY',
+ glCode: '310060',
+ glName: 'INVESTMENT IN SHARES',
+ crToAc: f.crToAc,
+ brokerCode: f.brokerCode,
+ status: 'ACTIVE'
+ };
+ this.holdings = [
+ holding,
+ ...this.holdings
+ ];
+ this.txns = [
+ {
+ certNo: holding.certNo,
+ instrumentType: 'SHARE',
+ txnType: 'PURCHASE',
+ voucherNo: `INV/PUR/${holding.certNo}`,
+ txnDate: f.purchaseDate,
+ amount: faceValue,
+ particulars: `Purchase of ${f.units} shares - ${holding.scriptName}`
+ },
+ ...this.txns
+ ];
+ this.toast(`Share holding ${holding.certNo} saved successfully.`);
+ this.purchaseForm = this.blankPurchaseForm();
+ this.activeTab = 'holdings';
+ }
+
+ cancelPurchase(): void {
+ this.purchaseForm = this.blankPurchaseForm();
+ }
+
+ // -------------------- Dividend --------------------
+ get dividendHolding(): InvestmentHolding | undefined {
+ return this.holdings.find((h) => h.certNo === this.dividendCertNo);
+ }
+
+ get dividendAmount(): number {
+ const h = this.dividendHolding;
+ if (!h || !this.dividendPerShare) {
+ return 0;
+ }
+ return Math.round(h.units * this.dividendPerShare);
+ }
+
+ saveDividend(): void {
+ const h = this.dividendHolding;
+ if (!h || !this.dividendPerShare) {
+ this.toast('Select a holding and enter dividend per share.');
+ return;
+ }
+ const amt = this.dividendAmount;
+ this.txns = [
+ {
+ certNo: h.certNo,
+ instrumentType: 'SHARE',
+ txnType: 'DIVIDEND',
+ voucherNo: `INV/DIV/${h.certNo}-${this.txns.length + 1}`,
+ txnDate: this.asOnDate,
+ amount: amt,
+ particulars: `Dividend received - ${h.scriptName} (₹${this.dividendPerShare}/share)`
+ },
+ ...this.txns
+ ];
+ this.toast(`Dividend of ₹${amt.toLocaleString('en-IN')} recorded for ${h.certNo}.`);
+ this.dividendCertNo = '';
+ this.dividendPerShare = null;
+ }
+
+ // -------------------- Sell --------------------
+ get sellHolding(): InvestmentHolding | undefined {
+ return this.holdings.find((h) => h.certNo === this.sellCertNo);
+ }
+
+ get sellConsideration(): number {
+ if (!this.sellUnits || !this.sellRate) {
+ return 0;
+ }
+ return Math.round(this.sellUnits * this.sellRate);
+ }
+
+ saveSell(): void {
+ const h = this.sellHolding;
+ if (!h || !this.sellUnits || !this.sellRate || this.sellUnits > h.units) {
+ this.toast('Select a holding and enter valid units (≤ held) and sell rate.');
+ return;
+ }
+ const consideration = this.sellConsideration;
+ h.units -= this.sellUnits;
+ h.faceValue = Math.round(h.units * h.purchaseRate);
+ if (h.units === 0) {
+ h.status = 'SOLD';
+ }
+ this.txns = [
+ {
+ certNo: h.certNo,
+ instrumentType: 'SHARE',
+ txnType: 'SELL',
+ voucherNo: `INV/SEL/${h.certNo}-${this.txns.length + 1}`,
+ txnDate: this.asOnDate,
+ amount: consideration,
+ particulars: `Sold ${this.sellUnits} shares of ${h.scriptName} @ ${this.sellRate}`
+ },
+ ...this.txns
+ ];
+ this.toast(`Sold ${this.sellUnits} shares. Consideration ₹${consideration.toLocaleString('en-IN')}.`);
+ this.sellCertNo = '';
+ this.sellUnits = null;
+ this.sellRate = null;
+ this.activeTab = 'holdings';
+ }
+
+ // -------------------- Write-off --------------------
+ get writeoffHolding(): InvestmentHolding | undefined {
+ return this.holdings.find((h) => h.certNo === this.writeoffCertNo);
+ }
+
+ saveWriteoff(): void {
+ const h = this.writeoffHolding;
+ if (!h || !this.writeoffReason) {
+ this.toast('Select a holding and enter a write-off reason.');
+ return;
+ }
+ h.status = 'WRITTEN_OFF';
+ this.txns = [
+ {
+ certNo: h.certNo,
+ instrumentType: 'SHARE',
+ txnType: 'WRITE_OFF',
+ voucherNo: `INV/WOF/${h.certNo}`,
+ txnDate: this.asOnDate,
+ amount: h.faceValue,
+ particulars: `Written off - ${this.writeoffReason}`
+ },
+ ...this.txns
+ ];
+ this.toast(`${h.certNo} written off.`);
+ this.writeoffCertNo = '';
+ this.writeoffReason = '';
+ this.activeTab = 'holdings';
+ }
+
+ statusClass(h: InvestmentHolding): string {
+ return `inv-badge inv-badge--${h.status.toLowerCase()}`;
+ }
+
+ private toast(msg: string): void {
+ this.toastMsg = msg;
+ this.toastVisible = true;
+ if (this.toastTimer) {
+ clearTimeout(this.toastTimer);
+ }
+ this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
+ }
+}
diff --git a/src/environments/.env.ts b/src/environments/.env.ts
index f94dce0fc..52cc1da1e 100644
--- a/src/environments/.env.ts
+++ b/src/environments/.env.ts
@@ -10,7 +10,7 @@
export default {
'mifos_x': {
'version': '260731',
- 'hash': 'ca85bae4'
+ 'hash': 'fd28788d'
},
'allow_switching_backend_instance': true
};