feat(investment,npa): RBI-grounded targets, GL-tracked interest posting, date/search pickers

- Correct Investment portfolio allocation and Loan Origination product-mix targets against
  RBI's UCB SLR/Non-SLR framework and PSL circulars, with a Target Basis column distinguishing
  RBI-mandated vs board-policy rows (Excel export included).
- Expand Investment holdings dataset with more near-term maturities so the Due-Date Diary
  reflects a realistic spread across FD/Govt Security/Non-SLR/Bond.
- Add From/To period-based Interest Calculation & Post (with GL selection for both posting and
  receiving) across the Investment Interest module, FD Entry and Bond Entry screens; fix a bug
  where a posted period kept re-appearing as due since accrual wasn't tracked per-holding.
- Add a reusable DD-MM-YYYY date-picker component and roll it out across all Investment date
  fields.
- NPA Summary Report: drop SMA-0/1/2 from the stage-wise table (kept in Day-wise Overdue only).
- NPA Recovery Report: add Account Type / NPA Category / Target-vs-Actual columns, a shared
  Account Type filter driving both the account picker and the register, and a searchable
  (fixed-position, not clipped by card overflow) account select for long lists.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 19:26:19 +05:30
co-authored by Claude Sonnet 5
parent d7f5bfe5f5
commit ebe67cc410
20 changed files with 1401 additions and 226 deletions
@@ -192,8 +192,8 @@
><span>Deal No</span><input type="text" [(ngModel)]="purchaseForm.dealNo" readonly
/></label>
<label class="inv-form-field"
><span>* Deal Date</span><input type="text" [(ngModel)]="purchaseForm.dealDate" placeholder="DD-MM-YYYY"
/></label>
><span>* Deal Date</span><mifosx-date-ddmmyyyy [(ngModel)]="purchaseForm.dealDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>* Deal Time (24 hr, HH:mm:ss)</span
><input type="text" [(ngModel)]="purchaseForm.dealTime" placeholder="HH:mm:ss"
@@ -221,8 +221,8 @@
</label>
<label class="inv-form-field"
><span>Settlement Dt</span
><input type="text" [(ngModel)]="purchaseForm.settlementDt" placeholder="DD-MM-YYYY"
/></label>
><mifosx-date-ddmmyyyy [(ngModel)]="purchaseForm.settlementDt"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field">
<span>* Party Code</span>
<select [(ngModel)]="purchaseForm.partyCode">
@@ -333,8 +333,8 @@
</select>
</label>
<label class="inv-form-field"
><span>* Issue Date</span><input type="text" [(ngModel)]="purchaseForm.issueDate" placeholder="DD-MM-YYYY"
/></label>
><span>* Issue Date</span><mifosx-date-ddmmyyyy [(ngModel)]="purchaseForm.issueDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>Period(Year)</span><input type="number" [(ngModel)]="purchaseForm.periodYear" min="0"
/></label>
@@ -354,8 +354,8 @@
><span>Call Option</span><input type="text" [(ngModel)]="purchaseForm.callOption"
/></label>
<label class="inv-form-field"
><span>Meet Dt</span><input type="text" [(ngModel)]="purchaseForm.meetDt" placeholder="DD-MM-YYYY"
/></label>
><span>Meet Dt</span><mifosx-date-ddmmyyyy [(ngModel)]="purchaseForm.meetDt"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>Reso No</span><input type="text" [(ngModel)]="purchaseForm.resoNo"
/></label>
@@ -394,11 +394,11 @@
</select>
</label>
<label class="inv-form-field"
><span>Int Date</span><input type="text" [(ngModel)]="purchaseForm.intDate" placeholder="DD-MM-YYYY"
/></label>
><span>Int Date</span><mifosx-date-ddmmyyyy [(ngModel)]="purchaseForm.intDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>* Last Int.dt</span><input type="text" [(ngModel)]="purchaseForm.lastIntDt" placeholder="DD-MM-YYYY"
/></label>
><span>* Last Int.dt</span><mifosx-date-ddmmyyyy [(ngModel)]="purchaseForm.lastIntDt"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>Accrued Interest (computed)</span
><input type="text" [value]="purchaseAccruedInterest | number: '1.2-2'" readonly
@@ -554,8 +554,8 @@
/></label>
}
<label class="inv-form-field"
><span>Redemption Date *</span><input type="text" [(ngModel)]="redeemDate" placeholder="DD-MM-YYYY"
/></label>
><span>Redemption Date *</span><mifosx-date-ddmmyyyy [(ngModel)]="redeemDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>Redemption Proceeds (computed)</span
><input type="text" [value]="'₹' + (redeemAmount | number: '1.2-2')" readonly
@@ -575,11 +575,14 @@
<div class="pos-section-label">Investment - Govt./Bond Interest calculation</div>
<div class="inv-form-grid">
<label class="inv-form-field"
><span>Date</span><input type="text" [(ngModel)]="intCalcDate" placeholder="DD-MM-YYYY"
/></label>
><span>From Date</span><mifosx-date-ddmmyyyy [(ngModel)]="intCalcFromDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>To Date</span><mifosx-date-ddmmyyyy [(ngModel)]="intCalcToDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field">
<span>Type</span>
<select [(ngModel)]="intCalcType">
<select [(ngModel)]="intCalcType" (ngModelChange)="onIntCalcTypeChange()">
<option value="">-- All Types --</option>
@for (o of bondTypeOptions; track o.value) {
<option [value]="o.value">{{ o.label }}</option>
@@ -599,11 +602,25 @@
<label class="inv-form-field"
><span>ALL</span><input type="checkbox" [(ngModel)]="intCalcAll" style="height: 18px; width: 18px"
/></label>
<label class="inv-form-field">
<span>Post To (Interest Income GL)</span>
<select [(ngModel)]="intCalcGlCode">
@for (g of interestIncomeGlOptions; track g.glCode) {
<option [value]="g.glCode">{{ g.glCode }} — {{ g.glName }}</option>
}
</select>
</label>
</div>
<div class="inv-form-actions">
<button type="button" class="pg-btn pg-btn--primary" (click)="processInterest()">Process</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="setTab('holdings')">Exit</button>
</div>
@if (intCalcExcludedNpi.length > 0) {
<p class="pg-sub" style="padding: 0 16px 12px">
{{ intCalcExcludedNpi.length }} holding(s) excluded — classified Non-Performing, interest is not posted on
accrual basis (collectible on cash basis only, via Interest Receive in the Investment Interest module).
</p>
}
<div class="pos-section-label">Holdings matching the filter ({{ intCalcTargets.length }})</div>
<table class="pg-table">
<thead>
@@ -694,8 +711,8 @@
><span>TBill GL.A/C.</span><input type="text" [(ngModel)]="sellForm.tbillGlAc"
/></label>
<label class="inv-form-field"
><span>* Deal Date</span><input type="text" [(ngModel)]="sellForm.dealDate" placeholder="DD-MM-YYYY"
/></label>
><span>* Deal Date</span><mifosx-date-ddmmyyyy [(ngModel)]="sellForm.dealDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>* Deal Time</span><input type="text" [(ngModel)]="sellForm.dealTime" placeholder="HH:mm:ss"
/></label>
@@ -708,8 +725,8 @@
</select>
</label>
<label class="inv-form-field"
><span>Settlement Dt</span><input type="text" [(ngModel)]="sellForm.settlementDt" placeholder="DD-MM-YYYY"
/></label>
><span>Settlement Dt</span><mifosx-date-ddmmyyyy [(ngModel)]="sellForm.settlementDt"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>* Face Value</span><input type="number" [(ngModel)]="sellForm.faceValue" min="0"
/></label>
@@ -942,8 +959,8 @@
</select>
</label>
<label class="inv-form-field"
><span>Transfer Date *</span><input type="text" [(ngModel)]="goiTransferDate" placeholder="DD-MM-YYYY"
/></label>
><span>Transfer Date *</span><mifosx-date-ddmmyyyy [(ngModel)]="goiTransferDate"></mifosx-date-ddmmyyyy
></label>
</div>
<div class="inv-form-actions">
<button type="button" class="pg-btn pg-btn--primary" (click)="saveGoiTransfer()">Save</button>
@@ -958,8 +975,8 @@
<div class="pos-section-label">Investment - Investment Depreciation (Mark-to-Market Provisioning)</div>
<div class="inv-form-grid">
<label class="inv-form-field"
><span>Valuation Date</span><input type="text" [(ngModel)]="depreciationDate" placeholder="DD-MM-YYYY"
/></label>
><span>Valuation Date</span><mifosx-date-ddmmyyyy [(ngModel)]="depreciationDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field">
<span>Category</span>
<select [(ngModel)]="depreciationCategory">
@@ -1043,8 +1060,8 @@
><span>Units to Transfer *</span><input type="number" [(ngModel)]="mfTransferUnits" min="0" step="0.001"
/></label>
<label class="inv-form-field"
><span>Transfer Date *</span><input type="text" [(ngModel)]="mfTransferDate" placeholder="DD-MM-YYYY"
/></label>
><span>Transfer Date *</span><mifosx-date-ddmmyyyy [(ngModel)]="mfTransferDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>Transfer Value (computed)</span
><input type="text" [value]="'₹' + (mfTransferValue | number: '1.2-2')" readonly
@@ -1069,6 +1086,7 @@
<th>Cert No</th>
<th>Type</th>
<th>Particulars</th>
<th>GL Posted</th>
<th class="num">Amount</th>
</tr>
</thead>
@@ -1082,11 +1100,12 @@
<td>{{ t.certNo }}</td>
<td>{{ t.txnType }}</td>
<td>{{ t.particulars }}</td>
<td>{{ t.glCode ? t.glCode + ' — ' + t.glName : '—' }}</td>
<td class="num">₹{{ t.amount | number: '1.2-2' }}</td>
</tr>
} @empty {
<tr>
<td colspan="6" class="pg-empty">No transactions yet.</td>
<td colspan="7" class="pg-empty">No transactions yet.</td>
</tr>
}
</tbody>
@@ -9,6 +9,7 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DateDdmmyyyyPickerComponent } from '../date-ddmmyyyy-picker/date-ddmmyyyy-picker.component';
import {
GOVT_SECURITY_HOLDINGS,
NON_SLR_HOLDINGS,
@@ -30,12 +31,16 @@ import {
BOND_RATING_OPTIONS,
INT_TYPE_OPTIONS,
AUTHORITY_OPTIONS,
INTEREST_INCOME_GL_MASTER,
INTEREST_INCOME_GL_BY_TYPE,
InvestmentHolding,
InvestmentTxn,
InstrumentType,
InterestFreq,
INSTRUMENT_LABELS,
accruedInterest,
accruedInterestBetween,
canAccrueInterest,
daysToMaturity,
maturityAmount,
addPeriod,
@@ -173,7 +178,8 @@ interface BondSellForm {
standalone: true,
imports: [
CommonModule,
FormsModule
FormsModule,
DateDdmmyyyyPickerComponent
]
})
export class BondInvestmentComponent {
@@ -224,12 +230,17 @@ export class BondInvestmentComponent {
redeemDate = this.asOnDate;
redeemRemarks = '';
// Interest Calculation (legacy: Date / Type / Script / Deal No / ALL + Process)
intCalcDate = this.asOnDate;
// Interest Calculation (legacy: From/To Date / Type / Script / Deal No / ALL + Process) —
// posts interest accrued strictly within [intCalcFromDate, intCalcToDate], same period-based
// basis as the Investment Interest module's Govt./Bond Interest Calculation.
intCalcFromDate = addPeriod(this.asOnDate, 0, -3);
intCalcToDate = this.asOnDate;
intCalcType: BondType | '' = '';
intCalcScript = '';
intCalcDealNo = '';
intCalcAll = false;
intCalcGlCode = INTEREST_INCOME_GL_BY_TYPE.GOVT_SECURITY;
readonly interestIncomeGlOptions = INTEREST_INCOME_GL_MASTER;
// Write-off
writeoffCertNo = '';
@@ -647,13 +658,15 @@ export class BondInvestmentComponent {
}
// -------------------- Interest calculation --------------------
/** Holdings the Interest Calculation "Process" run would cover, given the filters. */
/** Holdings the Interest Calculation "Process" run would cover, given the filters. A
* Non-Performing holding is always excluded — same accrual-basis rule as the Investment
* Interest module (it's still collectible on cash basis via Interest Receive there). */
get intCalcTargets(): InvestmentHolding[] {
const base = this.holdings.filter((h) => h.status === 'ACTIVE' && canAccrueInterest(h));
if (this.intCalcAll) {
return this.holdings.filter((h) => h.status === 'ACTIVE');
return base;
}
return this.holdings.filter((h) => {
if (h.status !== 'ACTIVE') return false;
return base.filter((h) => {
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;
@@ -661,31 +674,62 @@ export class BondInvestmentComponent {
});
}
get intCalcExcludedNpi(): InvestmentHolding[] {
return this.holdings.filter((h) => h.status === 'ACTIVE' && !canAccrueInterest(h));
}
/** Re-suggests the Interest Income GL when the Type filter changes — still overridable. */
onIntCalcTypeChange(): void {
if (this.intCalcType) {
this.intCalcGlCode = INTEREST_INCOME_GL_BY_TYPE[this.intCalcType];
}
}
glName(glCode: string): string {
return this.interestIncomeGlOptions.find((g) => g.glCode === glCode)?.glName ?? glCode;
}
/** Interest accrued strictly within [intCalcFromDate, intCalcToDate] — a period-based
* Interest Calculation & Post run, same basis as the Investment Interest module. */
interestAccrued(h: InvestmentHolding): number {
return accruedInterest(h, this.intCalcDate || this.asOnDate);
return accruedInterestBetween(h, this.intCalcFromDate || h.purchaseDate, this.intCalcToDate || this.asOnDate);
}
processInterest(): void {
const targets = this.intCalcTargets;
const targets = this.intCalcTargets.filter((h) => this.interestAccrued(h) > 0);
if (targets.length === 0) {
this.toast('No holdings match the selected filters.');
this.toast('No holdings have interest due to be posted for this period.');
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}`
}));
const toDate = this.intCalcToDate || this.asOnDate;
const fromDate = this.intCalcFromDate || toDate;
const glCode = this.intCalcGlCode;
const glName = this.glName(glCode);
const newTxns: InvestmentTxn[] = targets.map((h, i) => {
const amt = this.interestAccrued(h);
h.interestPosted = (h.interestPosted ?? 0) + amt;
h.interestPostedTillDate = toDate;
return {
certNo: h.certNo,
instrumentType: h.instrumentType,
txnType: 'INTEREST',
voucherNo: `INV/INT/${h.certNo}-${this.txns.length + i + 1}`,
txnDate: toDate,
amount: amt,
particulars: `Interest calculated (accrual) for period ${fromDate} to ${toDate} - ${h.scriptName}`,
glCode,
glName
};
});
this.txns = [
...newTxns,
...this.txns
];
this.intCalcFromDate = toDate;
const total = newTxns.reduce((s, t) => s + t.amount, 0);
this.toast(`Interest processed for ${targets.length} holding(s). Total ₹${total.toLocaleString('en-IN')}.`);
this.toast(
`Interest processed for ${targets.length} holding(s) to ${glCode} - ${glName}. Total ₹${total.toLocaleString('en-IN')}.`
);
}
// -------------------- Sell / Redeem Entry --------------------
@@ -0,0 +1,213 @@
/**
* 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, ElementRef, ViewChild, forwardRef } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
/**
* Drop-in replacement for `<input type="text" placeholder="DD-MM-YYYY">` used throughout the
* Investment module. Keeps the DD-MM-YYYY string every date field/helper in investment-data.ts
* already expects (parseDDMMYYYY, addPeriod, etc.), but adds a calendar button that opens the
* browser's native date picker and converts the picked value back to DD-MM-YYYY — so users can
* either type the date or pick it, without changing any consuming component's data model.
*
* Usage: <mifosx-date-ddmmyyyy [(ngModel)]="someField"></mifosx-date-ddmmyyyy>
*/
@Component({
selector: 'mifosx-date-ddmmyyyy',
standalone: true,
imports: [
CommonModule,
FormsModule
],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DateDdmmyyyyPickerComponent),
multi: true
}
],
template: `
<div class="inv-date-field">
<input
type="text"
class="inv-date-field__text"
[ngModel]="value"
(ngModelChange)="onTextChange($event)"
(blur)="onTouched()"
placeholder="DD-MM-YYYY"
[disabled]="disabled"
/>
<button
type="button"
class="inv-date-field__btn"
tabindex="-1"
[disabled]="disabled"
(click)="openPicker()"
aria-label="Pick a date"
title="Pick a date"
>
📅
</button>
<input
#native
type="date"
class="inv-date-field__native"
tabindex="-1"
[ngModel]="isoValue"
(ngModelChange)="onPickerChange($event)"
/>
</div>
`,
styles: [
`
/* Angular view encapsulation stops the host page's .inv-form-field styles from reaching
* inside this component's own template, so the look of the surrounding form fields
* (height, border, radius, font) is deliberately re-declared here instead of inherited. */
:host {
display: block;
width: 100%;
}
.inv-date-field {
position: relative;
display: flex;
align-items: center;
gap: 6px;
width: 100%;
}
.inv-date-field__text {
flex: 1;
min-width: 0;
width: 100%;
height: 36px;
padding: 0 10px;
border: 1px solid #e2e8f0;
border-radius: 8px;
font-size: 13px;
color: #0d1b2a;
font-family: inherit;
background: #fff;
box-sizing: border-box;
}
.inv-date-field__btn {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
height: 36px;
width: 36px;
padding: 0;
border: 1px solid #e2e8f0;
border-radius: 8px;
background: #fff;
cursor: pointer;
font-size: 14px;
line-height: 1;
}
.inv-date-field__btn:hover:not(:disabled) {
background: #f2f4f7;
}
.inv-date-field__btn:disabled {
cursor: not-allowed;
opacity: 0.5;
}
.inv-date-field__native {
position: absolute;
width: 0;
height: 0;
padding: 0;
border: none;
opacity: 0;
pointer-events: none;
}
`
]
})
export class DateDdmmyyyyPickerComponent implements ControlValueAccessor {
@ViewChild('native') private nativeInput?: ElementRef<HTMLInputElement>;
value = '';
disabled = false;
private onChangeFn: (v: string) => void = () => {};
onTouched: () => void = () => {};
/** DD-MM-YYYY -> yyyy-MM-dd, the format <input type="date"> requires. */
get isoValue(): string {
const parts = (this.value || '').split('-');
if (parts.length !== 3) {
return '';
}
const [
dd,
mm,
yyyy
] = parts;
if (!/^\d{2}$/.test(dd) || !/^\d{2}$/.test(mm) || !/^\d{4}$/.test(yyyy)) {
return '';
}
return `${yyyy}-${mm}-${dd}`;
}
writeValue(v: string): void {
this.value = v ?? '';
}
registerOnChange(fn: (v: string) => void): void {
this.onChangeFn = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
onTextChange(v: string): void {
this.value = v;
this.onChangeFn(v);
}
/** yyyy-MM-dd (from the native picker) -> DD-MM-YYYY, the format the rest of the app expects. */
onPickerChange(iso: string): void {
const parts = (iso || '').split('-');
if (parts.length !== 3) {
return;
}
const [
yyyy,
mm,
dd
] = parts;
const ddmmyyyy = `${dd}-${mm}-${yyyy}`;
this.value = ddmmyyyy;
this.onChangeFn(ddmmyyyy);
this.onTouched();
}
openPicker(): void {
const el = this.nativeInput?.nativeElement;
if (!el || this.disabled) {
return;
}
const showPicker = (el as HTMLInputElement & { showPicker?: () => void }).showPicker;
if (typeof showPicker === 'function') {
try {
showPicker.call(el);
return;
} catch {
// Some browsers throw if not called from a "user gesture" context they recognise —
// fall back to a plain click(), which still opens the native picker.
}
}
el.click();
}
}
@@ -179,8 +179,8 @@
><span>Certificate No.</span><input type="text" [(ngModel)]="purchaseForm.certNo"
/></label>
<label class="inv-form-field"
><span>Certificate Date</span><input type="text" [(ngModel)]="purchaseForm.certDate" placeholder="DD-MM-YYYY"
/></label>
><span>Certificate Date</span><mifosx-date-ddmmyyyy [(ngModel)]="purchaseForm.certDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field">
<span>Sub Type</span>
<select [(ngModel)]="purchaseForm.subType">
@@ -198,12 +198,12 @@
</label>
<label class="inv-form-field"
><span>Transaction Date</span
><input type="text" [(ngModel)]="purchaseForm.transactionDate" placeholder="DD-MM-YYYY"
/></label>
><mifosx-date-ddmmyyyy [(ngModel)]="purchaseForm.transactionDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>Deposit Date *</span
><input type="text" [(ngModel)]="purchaseForm.depositDate" placeholder="DD-MM-YYYY"
/></label>
><mifosx-date-ddmmyyyy [(ngModel)]="purchaseForm.depositDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>Deposit Amount *</span><input type="number" [(ngModel)]="purchaseForm.depositAmount" min="0"
/></label>
@@ -217,8 +217,8 @@
</label>
<label class="inv-form-field"
><span>Interest Date</span
><input type="text" [(ngModel)]="purchaseForm.interestDate" placeholder="DD-MM-YYYY"
/></label>
><mifosx-date-ddmmyyyy [(ngModel)]="purchaseForm.interestDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>Interest Rate *</span><input type="number" [(ngModel)]="purchaseForm.interestRate" min="0" step="0.01"
/></label>
@@ -235,8 +235,8 @@
} @else {
<label class="inv-form-field"
><span>Maturity Date (manual) *</span
><input type="text" [(ngModel)]="purchaseForm.manualMaturityDate" placeholder="DD-MM-YYYY"
/></label>
><mifosx-date-ddmmyyyy [(ngModel)]="purchaseForm.manualMaturityDate"></mifosx-date-ddmmyyyy
></label>
}
<label class="inv-form-field"
><span>Resolution No.</span><input type="text" [(ngModel)]="purchaseForm.resolutionNo"
@@ -327,11 +327,25 @@
<span>Certificate No *</span>
<select [(ngModel)]="interestCertNo">
<option value="">-- Select --</option>
@for (h of activeHoldings; track h.certNo) {
@for (h of interestEligibleHoldings; track h.certNo) {
<option [value]="h.certNo">{{ h.certNo }} — {{ h.party }}</option>
}
</select>
</label>
<label class="inv-form-field"
><span>From Date</span><mifosx-date-ddmmyyyy [(ngModel)]="interestFromDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>To Date</span><mifosx-date-ddmmyyyy [(ngModel)]="interestToDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field">
<span>Post To (Interest Income GL)</span>
<select [(ngModel)]="interestGlCode">
@for (g of interestIncomeGlOptions; track g.glCode) {
<option [value]="g.glCode">{{ g.glCode }} — {{ g.glName }}</option>
}
</select>
</label>
@if (interestHolding; as h) {
<label class="inv-form-field"><span>Party</span><input type="text" [value]="h.party" readonly /></label>
<label class="inv-form-field"
@@ -341,7 +355,7 @@
><span>Frequency</span><input type="text" [value]="h.interestFreq" readonly
/></label>
<label class="inv-form-field"
><span>Accrued Interest (as on {{ asOnDate }})</span
><span>Interest for Period</span
><input type="text" [value]="'₹' + (interestAccrued(h) | number: '1.2-2')" readonly
/></label>
}
@@ -376,8 +390,8 @@
/></label>
}
<label class="inv-form-field"
><span>Sell / Premature Date *</span><input type="text" [(ngModel)]="sellDate" placeholder="DD-MM-YYYY"
/></label>
><span>Sell / Premature Date *</span><mifosx-date-ddmmyyyy [(ngModel)]="sellDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"><span>Remarks</span><input type="text" [(ngModel)]="sellRemark" /></label>
</div>
<div class="inv-form-actions">
@@ -454,6 +468,7 @@
<th>Cert No</th>
<th>Type</th>
<th>Particulars</th>
<th>GL Posted</th>
<th class="num">Amount</th>
</tr>
</thead>
@@ -467,6 +482,7 @@
<td>{{ t.certNo }}</td>
<td>{{ t.txnType }}</td>
<td>{{ t.particulars }}</td>
<td>{{ t.glCode ? t.glCode + ' — ' + t.glName : '—' }}</td>
<td class="num">₹{{ t.amount | number: '1.2-2' }}</td>
</tr>
}
@@ -9,6 +9,7 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DateDdmmyyyyPickerComponent } from '../date-ddmmyyyy-picker/date-ddmmyyyy-picker.component';
import {
FD_HOLDINGS,
ALL_TXNS,
@@ -17,10 +18,14 @@ import {
FD_TYPE_OPTIONS,
INT_TYPE_OPTIONS,
LIEN_REASON_MASTER,
INTEREST_INCOME_GL_MASTER,
INTEREST_INCOME_GL_BY_TYPE,
InvestmentHolding,
InvestmentTxn,
InterestFreq,
accruedInterest,
accruedInterestBetween,
canAccrueInterest,
daysToMaturity,
maturityAmount,
addPeriod,
@@ -83,7 +88,8 @@ interface FdPurchaseForm {
standalone: true,
imports: [
CommonModule,
FormsModule
FormsModule,
DateDdmmyyyyPickerComponent
]
})
export class FdInvestmentComponent {
@@ -98,6 +104,7 @@ export class FdInvestmentComponent {
readonly fdTypeOptions = FD_TYPE_OPTIONS;
readonly intTypeOptions = INT_TYPE_OPTIONS;
readonly lienReasonOptions = LIEN_REASON_MASTER;
readonly interestIncomeGlOptions = INTEREST_INCOME_GL_MASTER;
readonly branchOptions = [
'HEAD OFFICE',
'BRANCH 1',
@@ -114,6 +121,9 @@ export class FdInvestmentComponent {
searchCertNo = '';
interestCertNo = '';
interestFromDate = addPeriod(this.asOnDate, 0, -3);
interestToDate = this.asOnDate;
interestGlCode = INTEREST_INCOME_GL_BY_TYPE.FD;
sellCertNo = '';
sellDate = this.asOnDate;
sellRemark = '';
@@ -313,12 +323,24 @@ export class FdInvestmentComponent {
}
// -------------------- Interest --------------------
/** Certificates eligible for accrual-basis posting — same rule as the Investment Interest
* module: a Non-Performing holding never appears here, only through Interest Receive. */
get interestEligibleHoldings(): InvestmentHolding[] {
return this.activeHoldings.filter((h) => canAccrueInterest(h));
}
get interestHolding(): InvestmentHolding | undefined {
return this.holdings.find((h) => h.certNo === this.interestCertNo);
}
/** Interest accrued strictly within [interestFromDate, interestToDate] — a period-based
* Interest Calculation & Post run, same basis as the Investment Interest module. */
interestAccrued(h: InvestmentHolding): number {
return accruedInterest(h, this.asOnDate);
return accruedInterestBetween(h, this.interestFromDate || h.purchaseDate, this.interestToDate || this.asOnDate);
}
glName(glCode: string): string {
return this.interestIncomeGlOptions.find((g) => g.glCode === glCode)?.glName ?? glCode;
}
postInterest(): void {
@@ -328,19 +350,34 @@ export class FdInvestmentComponent {
return;
}
const amt = this.interestAccrued(h);
if (amt <= 0) {
this.toast('No interest due to be posted for this period.');
return;
}
const toDate = this.interestToDate || this.asOnDate;
const fromDate = this.interestFromDate || toDate;
const glCode = this.interestGlCode;
const glName = this.glName(glCode);
h.interestPosted = (h.interestPosted ?? 0) + amt;
h.interestPostedTillDate = toDate;
this.interestFromDate = toDate;
this.txns = [
{
certNo: h.certNo,
instrumentType: 'FD',
txnType: 'INTEREST',
voucherNo: `INV/INT/FD-${h.certNo}-${this.txns.length + 1}`,
txnDate: this.asOnDate,
txnDate: toDate,
amount: amt,
particulars: `Interest posted - ${h.party} (${h.interestFreq})`
particulars: `Interest posted (accrual) for period ${fromDate} to ${toDate} - ${h.party} (${h.interestFreq})`,
glCode,
glName
},
...this.txns
];
this.toast(`Interest of ₹${amt.toLocaleString('en-IN')} posted for Certificate ${h.certNo}.`);
this.toast(
`Interest of ₹${amt.toLocaleString('en-IN')} posted to ${glCode} - ${glName} for Certificate ${h.certNo}.`
);
this.interestCertNo = '';
}
@@ -85,10 +85,16 @@
<div class="pg-card">
<div class="pos-section-label">Portfolio Allocation — Target vs Actual</div>
<p class="pg-sub">
Govt Security, Non-SLR, Mutual Fund and Share targets trace to RBI's SLR floor (18% of NDTL) and Non-SLR ceiling
(≤10% of deposits) for Urban Co-operative Banks; Fixed Deposit and Bond are board-approved policy choices within
the room that framework leaves.
</p>
<table class="pg-table">
<thead>
<tr>
<th>Instrument Type</th>
<th>Target Basis</th>
<th class="num">Target %</th>
<th class="num">Actual %</th>
<th class="num">Actual Value</th>
@@ -100,6 +106,11 @@
@for (a of allocationVsTarget; track a.type) {
<tr>
<td>{{ a.label }}</td>
<td>
<span class="inv-badge" [class.inv-badge--matured]="a.isRbiLinked">
{{ a.isRbiLinked ? 'RBI SLR / Non-SLR Norm' : 'Board Policy' }}
</span>
</td>
<td class="num">{{ a.targetPct }}%</td>
<td class="num">{{ a.actualPct }}%</td>
<td class="num">₹{{ a.actualValue | number: '1.2-2' }}</td>
@@ -11,6 +11,7 @@ import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';
import {
ALL_HOLDINGS,
ALLOCATION_TARGET_IS_RBI_LINKED,
INSTRUMENT_LABELS,
InstrumentType,
InvestmentHolding,
@@ -39,6 +40,9 @@ interface AllocationRow {
actualValue: number;
variancePct: number;
statusLabel: 'Within Target' | 'Over-Allocated' | 'Under-Allocated';
/** True when the target itself traces to an RBI SLR/Non-SLR rule rather than being a
* board-only policy choice — see TARGET_ALLOCATION_PCT's doc comment in investment-data.ts. */
isRbiLinked: boolean;
}
/** Cross-instrument portfolio dashboard — every figure is derived from ALL_HOLDINGS, never
@@ -114,7 +118,8 @@ export class InvestmentDashboardComponent {
actualPct: +actualPct.toFixed(1),
actualValue: value,
variancePct,
statusLabel
statusLabel,
isRbiLinked: ALLOCATION_TARGET_IS_RBI_LINKED[type]
};
});
}
@@ -195,6 +200,7 @@ export class InvestmentDashboardComponent {
subtitle: `As on ${this.asOnDate}`,
headers: [
'Instrument Type',
'Target Basis',
'Target %',
'Actual %',
'Actual Value',
@@ -203,6 +209,7 @@ export class InvestmentDashboardComponent {
],
widths: [
26,
20,
12,
12,
18,
@@ -211,6 +218,7 @@ export class InvestmentDashboardComponent {
],
rows: this.allocationVsTarget.map((a) => [
a.label,
a.isRbiLinked ? 'RBI SLR / Non-SLR Norm' : 'Board Policy',
a.targetPct,
a.actualPct,
a.actualValue,
+361 -17
View File
@@ -53,16 +53,45 @@ export const PROVISION_STATUS_LABELS: Record<InvestmentProvisionStatus, string>
LOSS: 'Loss (NPI)'
};
/** Board-approved portfolio-policy target — % of the total investment corpus each instrument
* type should hold. Compared against actual holdings on the Dashboard so a treasury desk can
* see at a glance where the book has drifted from policy. */
/**
* Portfolio-policy target — % of the total investment corpus each instrument type should hold.
* Compared against actual holdings on the Dashboard so a treasury desk can see at a glance where
* the book has drifted from policy. Four of these six trace back to real RBI rules for Urban
* Co-operative Banks (RBI Master Direction/Circular — Investment Portfolio of Primary (Urban)
* Co-operative Banks):
* • SLR: UCBs must hold 18% of NDTL in approved securities — predominantly Central/State Govt
* securities (and SLR-eligible State Development Loan bonds). A well-run treasury holds a
* safety buffer above the bare floor, so Govt Security dominates the book far beyond the 18%
* minimum — it isn't itself an upper target, just the reason this bucket is the largest.
* • Non-SLR ceiling: a UCB's Non-SLR investments must not exceed 10% of its total deposits as
* on 31 March of the previous year — a hard regulatory cap, not a board choice.
* • Mutual Funds: RBI permits UCBs to invest only in debt-oriented schemes of SEBI-registered
* mutual funds, in strictly limited amounts — equity/balanced schemes are not permitted.
* • Shares: RBI does not permit UCBs to invest in equity shares of companies as a general
* investment class; any "Share" holding is essentially the (small, largely static) shares a
* UCB holds in affiliated co-operative institutions, not a discretionary trading book.
* Fixed Deposit (treasury placements with other banks) and Bond (a mix of SLR-eligible SDL and
* Non-SLR PSU/FI bonds) have no RBI-mandated percentage of their own — their split here is a
* board-approved policy choice within the room RBI's SLR/Non-SLR framework leaves.
*/
export const TARGET_ALLOCATION_PCT: Record<InstrumentType, number> = {
GOVT_SECURITY: 55,
BOND: 18,
FD: 15,
GOVT_SECURITY: 35,
NON_SLR: 10,
BOND: 20,
MUTUAL_FUND: 12,
SHARE: 8
NON_SLR: 7,
MUTUAL_FUND: 3,
SHARE: 2
};
/** True for the instrument types whose target traces to an actual RBI rule (SLR floor / Non-SLR
* ceiling) rather than a board-only policy choice — see TARGET_ALLOCATION_PCT's doc comment. */
export const ALLOCATION_TARGET_IS_RBI_LINKED: Record<InstrumentType, boolean> = {
GOVT_SECURITY: true,
BOND: false,
FD: false,
NON_SLR: true,
MUTUAL_FUND: true,
SHARE: true
};
export interface InvestmentHolding {
@@ -90,6 +119,10 @@ export interface InvestmentHolding {
interestPosted?: number;
/** Cumulative interest actually received in cash to date. */
interestReceived?: number;
/** DD-MM-YYYY through which interest has already been calculated & posted. accruedInterestBetween()
* never re-counts days up to this date, so re-running a Calculation & Post for an overlapping
* or repeated period always yields 0 instead of double-posting the same interest. */
interestPostedTillDate?: string;
}
export interface InvestmentTxn {
@@ -100,6 +133,10 @@ export interface InvestmentTxn {
txnDate: string;
amount: number;
particulars: string;
/** GL account this entry was posted/received against — the Interest Income GL for a Post,
* the Cash/Bank GL for a Receive. Absent on older/seeded entries that predate GL selection. */
glCode?: string;
glName?: string;
}
// -------------------- Date / interest helpers --------------------
@@ -175,15 +212,6 @@ export function canAccrueInterest(h: InvestmentHolding): boolean {
return !isNpi(h);
}
/** Interest still to be calculated & posted this cycle. Always 0 for an NPI holding — posting
* an accrual on a non-performing investment would overstate income. */
export function interestToPost(h: InvestmentHolding, asOnStr: string): number {
if (!canAccrueInterest(h)) {
return 0;
}
return Math.max(0, accruedInterest(h, asOnStr) - (h.interestPosted ?? 0));
}
/** Outstanding "Interest Receivable" GL balance — interest already posted (booked as income)
* but not yet actually received in cash. Always 0 for an NPI holding, since nothing is ever
* posted as a receivable on one. */
@@ -203,6 +231,23 @@ export function suspenseInterest(h: InvestmentHolding, asOnStr: string): number
return Math.max(0, accruedInterest(h, asOnStr) - (h.interestReceived ?? 0));
}
/** Interest accrued strictly within [fromStr, toStr], clipped to the holding's own
* purchase→maturity window — the basis for a period-based Interest Calculation & Post run
* (as opposed to accruedInterest(), which is always measured from purchase date). Returns 0
* for a Non-Performing holding, same accrual-basis rule as accruedInterest(). */
export function accruedInterestBetween(h: InvestmentHolding, fromStr: string, toStr: string): number {
if (!canAccrueInterest(h)) {
return 0;
}
const purchase = parseDDMMYYYY(h.purchaseDate).getTime();
const maturity = parseDDMMYYYY(h.maturityDate).getTime();
const postedTill = h.interestPostedTillDate ? parseDDMMYYYY(h.interestPostedTillDate).getTime() : purchase;
const from = Math.max(parseDDMMYYYY(fromStr).getTime(), purchase, postedTill);
const to = Math.min(parseDDMMYYYY(toStr).getTime(), maturity);
const days = Math.max(0, (to - from) / (24 * 3600 * 1000));
return Math.round(h.faceValue * (h.interestRate / 100) * (days / 365.25));
}
export function formatDDMMYYYY(d: Date): string {
return `${String(d.getDate()).padStart(2, '0')}-${String(d.getMonth() + 1).padStart(2, '0')}-${d.getFullYear()}`;
}
@@ -388,6 +433,68 @@ export const FD_HOLDINGS: InvestmentHolding[] = [
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
},
{
certNo: '15628',
instrumentType: 'FD',
scriptCode: 'FD-CUM',
scriptName: 'Cumulative Fixed Deposit',
party: 'Zilla Sahakari Bank Ltd',
subType: 'NON SLR',
faceValue: 1800000,
purchaseRate: 100,
units: 1,
purchaseDate: '20-08-2025',
maturityDate: '20-08-2026',
interestRate: 6.8,
interestFreq: 'CUMULATIVE',
glCode: '320020',
glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
},
{
certNo: '15629',
instrumentType: 'FD',
scriptCode: 'FD-MIP',
scriptName: 'Monthly Interest Payout FD',
party: 'Vidarbha Cooperative Bank',
subType: 'NON SLR',
faceValue: 2200000,
purchaseRate: 100,
units: 1,
purchaseDate: '25-09-2025',
maturityDate: '25-09-2026',
interestRate: 7.15,
interestFreq: 'MONTHLY',
glCode: '320020',
glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE',
interestPosted: 143220,
interestReceived: 130800
},
{
certNo: '15630',
instrumentType: 'FD',
scriptCode: 'FD-CUM',
scriptName: 'Cumulative Fixed Deposit',
party: 'Konkan Cooperative Bank Ltd',
subType: 'NON SLR',
faceValue: 900000,
purchaseRate: 100,
units: 1,
purchaseDate: '28-10-2024',
maturityDate: '28-10-2026',
interestRate: 6.75,
interestFreq: 'YEARLY',
glCode: '320020',
glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
}
];
@@ -515,6 +622,46 @@ export const GOVT_SECURITY_HOLDINGS: InvestmentHolding[] = [
crToAc: '310070',
brokerCode: 'BRK-002',
status: 'ACTIVE'
},
{
certNo: 'GSEC-2007',
instrumentType: 'GOVT_SECURITY',
scriptCode: 'TBILL-91D',
scriptName: '91 Days Treasury Bill',
party: 'RBI - SGL',
subType: 'SLR',
faceValue: 2000000,
purchaseRate: 98.2,
units: 20000,
purchaseDate: '15-05-2026',
maturityDate: '13-08-2026',
interestRate: 6.2,
interestFreq: 'CUMULATIVE',
glCode: '310020',
glName: 'INVESTMENT IN GOVT. SECURITIES',
crToAc: '310070',
brokerCode: 'BRK-003',
status: 'ACTIVE'
},
{
certNo: 'GSEC-2008',
instrumentType: 'GOVT_SECURITY',
scriptCode: 'GJ-SDL-2026',
scriptName: '7.85% Gujarat SDL 2026',
party: 'RBI - SGL',
subType: 'SLR',
faceValue: 1800000,
purchaseRate: 100.2,
units: 18000,
purchaseDate: '18-09-2025',
maturityDate: '18-09-2026',
interestRate: 7.85,
interestFreq: 'HALF_YEARLY',
glCode: '310020',
glName: 'INVESTMENT IN GOVT. SECURITIES',
crToAc: '310070',
brokerCode: 'BRK-001',
status: 'ACTIVE'
}
];
@@ -620,6 +767,46 @@ export const NON_SLR_HOLDINGS: InvestmentHolding[] = [
status: 'ACTIVE',
interestPosted: 89400,
interestReceived: 60000
},
{
certNo: 'NSLR-3006',
instrumentType: 'NON_SLR',
scriptCode: 'IRFC-7.55-2026',
scriptName: 'IRFC Bond 7.55% 2026',
party: 'Indian Railway Finance Corp',
subType: 'NON SLR',
faceValue: 1600000,
purchaseRate: 100,
units: 16000,
purchaseDate: '25-08-2025',
maturityDate: '25-08-2026',
interestRate: 7.55,
interestFreq: 'YEARLY',
glCode: '310030',
glName: 'INVESTMENT IN NON SLR SECURITIES',
crToAc: '310070',
brokerCode: 'BRK-004',
status: 'ACTIVE'
},
{
certNo: 'NSLR-3007',
instrumentType: 'NON_SLR',
scriptCode: 'NHB-7.3-2026',
scriptName: 'NHB Bond 7.30% 2026',
party: 'National Housing Bank',
subType: 'NON SLR',
faceValue: 1000000,
purchaseRate: 100,
units: 10000,
purchaseDate: '10-10-2025',
maturityDate: '10-10-2026',
interestRate: 7.3,
interestFreq: 'YEARLY',
glCode: '310030',
glName: 'INVESTMENT IN NON SLR SECURITIES',
crToAc: '310070',
brokerCode: 'BRK-003',
status: 'ACTIVE'
}
];
@@ -726,6 +913,46 @@ export const BOND_HOLDINGS: InvestmentHolding[] = [
crToAc: '310070',
brokerCode: 'BRK-005',
status: 'ACTIVE'
},
{
certNo: 'BOND-4006',
instrumentType: 'BOND',
scriptCode: 'CHOLA-8.35-2026',
scriptName: 'Cholamandalam Finance NCD 8.35% 2026',
party: 'Cholamandalam Investment & Finance',
subType: 'CORPORATE BOND',
faceValue: 1400000,
purchaseRate: 100,
units: 14000,
purchaseDate: '15-09-2024',
maturityDate: '15-09-2026',
interestRate: 8.35,
interestFreq: 'YEARLY',
glCode: '310040',
glName: 'INVESTMENT IN CORPORATE BONDS',
crToAc: '310070',
brokerCode: 'BRK-006',
status: 'ACTIVE'
},
{
certNo: 'BOND-4007',
instrumentType: 'BOND',
scriptCode: 'PEL-8.4-2026',
scriptName: 'Piramal Enterprises NCD 8.40% 2026',
party: 'Piramal Enterprises Ltd',
subType: 'CORPORATE BOND',
faceValue: 900000,
purchaseRate: 100,
units: 9000,
purchaseDate: '28-10-2025',
maturityDate: '28-10-2026',
interestRate: 8.4,
interestFreq: 'YEARLY',
glCode: '310040',
glName: 'INVESTMENT IN CORPORATE BONDS',
crToAc: '310070',
brokerCode: 'BRK-005',
status: 'ACTIVE'
}
];
@@ -829,6 +1056,46 @@ export const MUTUAL_FUND_HOLDINGS: InvestmentHolding[] = [
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
},
{
certNo: 'MF-5006',
instrumentType: 'MUTUAL_FUND',
scriptCode: 'SBI-BPSU',
scriptName: 'SBI Banking & PSU Fund',
party: 'SBI Mutual Fund',
subType: 'DEBT - BANKING & PSU',
faceValue: 700000,
purchaseRate: 18.6,
units: 37634.4,
purchaseDate: '10-09-2025',
maturityDate: '10-09-2026',
interestRate: 7.1,
interestFreq: 'CUMULATIVE',
glCode: '310050',
glName: 'INVESTMENT IN MUTUAL FUNDS',
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
},
{
certNo: 'MF-5007',
instrumentType: 'MUTUAL_FUND',
scriptCode: 'ICICI-CB',
scriptName: 'ICICI Pru Corporate Bond Fund',
party: 'ICICI Prudential Mutual Fund',
subType: 'DEBT - CORPORATE BOND',
faceValue: 1100000,
purchaseRate: 24.8,
units: 44354.84,
purchaseDate: '14-07-2026',
maturityDate: '14-07-2027',
interestRate: 7.05,
interestFreq: 'CUMULATIVE',
glCode: '310050',
glName: 'INVESTMENT IN MUTUAL FUNDS',
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
}
];
@@ -932,6 +1199,46 @@ export const SHARE_HOLDINGS: InvestmentHolding[] = [
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
},
{
certNo: 'SH-6006',
instrumentType: 'SHARE',
scriptCode: 'ICICIBANK',
scriptName: 'ICICI Bank Ltd',
party: 'NSE/BSE',
subType: 'EQUITY',
faceValue: 420000,
purchaseRate: 1050,
units: 400,
purchaseDate: '05-03-2026',
maturityDate: '05-03-2036',
interestRate: 2.0,
interestFreq: 'YEARLY',
glCode: '310060',
glName: 'INVESTMENT IN SHARES',
crToAc: '310070',
brokerCode: 'BRK-007',
status: 'ACTIVE'
},
{
certNo: 'SH-6007',
instrumentType: 'SHARE',
scriptCode: 'PDCC',
scriptName: 'Pune District Central Coop Bank Shares',
party: 'Pune District Central Cooperative Bank',
subType: 'CO-OP SHARE',
faceValue: 60000,
purchaseRate: 50,
units: 1200,
purchaseDate: '18-07-2025',
maturityDate: '18-07-2035',
interestRate: 5.0,
interestFreq: 'YEARLY',
glCode: '310060',
glName: 'INVESTMENT IN SHARES',
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
}
];
@@ -1060,6 +1367,43 @@ export const ALL_TXNS: InvestmentTxn[] = [
// -------------------- Masters --------------------
export interface InvestmentGlOption {
glCode: string;
glName: string;
}
/** Interest Income (Cr) GL per instrument type — the GL an Interest Calculation & Post run
* credits. Selectable on the FD/Govt./Bond Interest Calculation screens instead of being
* hard-wired, so a period's posting can be routed to a different income head when needed. */
export const INTEREST_INCOME_GL_MASTER: InvestmentGlOption[] = [
{ glCode: '410010', glName: 'INTEREST ON GOVT. SECURITIES' },
{ glCode: '410020', glName: 'INTEREST ON NON SLR SECURITIES' },
{ glCode: '410030', glName: 'INTEREST ON CORPORATE BONDS' },
{ glCode: '410040', glName: 'INTEREST ON FIXED DEPOSITS' },
{ glCode: '410050', glName: 'INCOME FROM MUTUAL FUNDS' },
{ glCode: '410060', glName: 'DIVIDEND ON SHARES' }
];
/** Default Interest Income GL suggested per instrument type — pre-fills the selector above,
* which the user can still override before posting. */
export const INTEREST_INCOME_GL_BY_TYPE: Record<InstrumentType, string> = {
GOVT_SECURITY: '410010',
NON_SLR: '410020',
BOND: '410030',
FD: '410040',
MUTUAL_FUND: '410050',
SHARE: '410060'
};
/** Cash/Bank (Dr) GL credited when an Interest Receive entry is booked — the account the cash
* actually lands in. Selectable on the Interest Receive screen. */
export const CASH_BANK_GL_MASTER: InvestmentGlOption[] = [
{ glCode: '100010', glName: 'CASH IN HAND' },
{ glCode: '110010', glName: 'RBI CURRENT ACCOUNT' },
{ glCode: '110020', glName: 'SBI CURRENT ACCOUNT' },
{ glCode: '110030', glName: 'HDFC BANK CURRENT ACCOUNT' }
];
export interface ScriptMaster {
scriptCode: string;
scriptName: string;
@@ -184,8 +184,11 @@
<div class="pos-section-label">Investment - FD Interest Calculation &amp; Post</div>
<div class="inv-form-grid">
<label class="inv-form-field"
><span>Date</span><input type="text" [(ngModel)]="fdCalcDate" placeholder="DD-MM-YYYY"
/></label>
><span>From Date</span><mifosx-date-ddmmyyyy [(ngModel)]="fdCalcFromDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>To Date</span><mifosx-date-ddmmyyyy [(ngModel)]="fdCalcToDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field">
<span>Certificate No</span>
<select [(ngModel)]="fdCalcCertNo">
@@ -198,6 +201,14 @@
<label class="inv-form-field"
><span>ALL</span><input type="checkbox" [(ngModel)]="fdCalcAll" style="height: 18px; width: 18px"
/></label>
<label class="inv-form-field">
<span>Post To (Interest Income GL)</span>
<select [(ngModel)]="fdCalcGlCode">
@for (g of interestIncomeGlOptions; track g.glCode) {
<option [value]="g.glCode">{{ g.glCode }} — {{ g.glName }}</option>
}
</select>
</label>
<label class="inv-form-field"
><span>Total Interest to Post</span
><input type="text" [value]="'₹' + (fdCalcTotal | number: '1.2-2')" readonly
@@ -257,11 +268,14 @@
<div class="pos-section-label">Investment - Govt./Bond Interest Calculation &amp; Post</div>
<div class="inv-form-grid">
<label class="inv-form-field"
><span>Date</span><input type="text" [(ngModel)]="bondCalcDate" placeholder="DD-MM-YYYY"
/></label>
><span>From Date</span><mifosx-date-ddmmyyyy [(ngModel)]="bondCalcFromDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>To Date</span><mifosx-date-ddmmyyyy [(ngModel)]="bondCalcToDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field">
<span>Type</span>
<select [(ngModel)]="bondCalcType">
<select [(ngModel)]="bondCalcType" (ngModelChange)="onBondCalcTypeChange()">
<option value="">-- All Types --</option>
<option value="GOVT_SECURITY">GOVT SECURITY</option>
<option value="NON_SLR">NON SLR</option>
@@ -281,6 +295,14 @@
<label class="inv-form-field"
><span>ALL</span><input type="checkbox" [(ngModel)]="bondCalcAll" style="height: 18px; width: 18px"
/></label>
<label class="inv-form-field">
<span>Post To (Interest Income GL)</span>
<select [(ngModel)]="bondCalcGlCode">
@for (g of interestIncomeGlOptions; track g.glCode) {
<option [value]="g.glCode">{{ g.glCode }} — {{ g.glName }}</option>
}
</select>
</label>
<label class="inv-form-field"
><span>Total Interest to Post</span
><input type="text" [value]="'₹' + (bondCalcTotal | number: '1.2-2')" readonly
@@ -342,6 +364,16 @@
@if (activeTab === 'receive') {
<div class="pg-card">
<div class="pos-section-label">Interest Receive — Record Actual Cash Receipt</div>
<div class="inv-form-grid">
<label class="inv-form-field">
<span>Receive Into (Cash/Bank GL)</span>
<select [(ngModel)]="receiveGlCode">
@for (g of cashBankGlOptions; track g.glCode) {
<option [value]="g.glCode">{{ g.glCode }} — {{ g.glName }}</option>
}
</select>
</label>
</div>
<table class="pg-table">
<thead>
<tr>
@@ -396,6 +428,7 @@
<th>Voucher No</th>
<th>Cert No</th>
<th>Particulars</th>
<th>GL Credited</th>
<th class="num col-in">Amount Received</th>
</tr>
</thead>
@@ -408,17 +441,18 @@
</td>
<td>{{ t.certNo }}</td>
<td>{{ t.particulars }}</td>
<td>{{ t.glCode ? t.glCode + ' — ' + t.glName : '—' }}</td>
<td class="num col-in">₹{{ t.amount | number: '1.2-2' }}</td>
</tr>
} @empty {
<tr>
<td colspan="5" class="pg-empty">No interest received yet.</td>
<td colspan="6" class="pg-empty">No interest received yet.</td>
</tr>
}
</tbody>
<tfoot>
<tr class="cctr-total-row">
<td colspan="4">Total Received</td>
<td colspan="5">Total Received</td>
<td class="num">₹{{ receivedLogTotal | number: '1.2-2' }}</td>
</tr>
</tfoot>
@@ -438,6 +472,7 @@
<th>Cert No</th>
<th>Instrument</th>
<th>Particulars</th>
<th>GL Posted</th>
<th class="num col-in">Interest Amount</th>
</tr>
</thead>
@@ -451,17 +486,18 @@
<td>{{ t.certNo }}</td>
<td>{{ instrumentLabel(t.instrumentType) }}</td>
<td>{{ t.particulars }}</td>
<td>{{ t.glCode ? t.glCode + ' — ' + t.glName : '—' }}</td>
<td class="num col-in">₹{{ t.amount | number: '1.2-2' }}</td>
</tr>
} @empty {
<tr>
<td colspan="6" class="pg-empty">No bond interest entries recorded yet.</td>
<td colspan="7" class="pg-empty">No bond interest entries recorded yet.</td>
</tr>
}
</tbody>
<tfoot>
<tr class="cctr-total-row">
<td colspan="5">Total</td>
<td colspan="6">Total</td>
<td class="num">₹{{ bondIntRegisterTotal | number: '1.2-2' }}</td>
</tr>
</tfoot>
@@ -480,6 +516,7 @@
<th>Voucher No</th>
<th>Cert No</th>
<th>Particulars</th>
<th>GL Posted</th>
<th class="num col-in">Interest Amount</th>
</tr>
</thead>
@@ -492,17 +529,18 @@
</td>
<td>{{ t.certNo }}</td>
<td>{{ t.particulars }}</td>
<td>{{ t.glCode ? t.glCode + ' — ' + t.glName : '—' }}</td>
<td class="num col-in">₹{{ t.amount | number: '1.2-2' }}</td>
</tr>
} @empty {
<tr>
<td colspan="5" class="pg-empty">No FD interest entries recorded yet.</td>
<td colspan="6" class="pg-empty">No FD interest entries recorded yet.</td>
</tr>
}
</tbody>
<tfoot>
<tr class="cctr-total-row">
<td colspan="4">Total</td>
<td colspan="5">Total</td>
<td class="num">₹{{ fdIntRegisterTotal | number: '1.2-2' }}</td>
</tr>
</tfoot>
@@ -9,6 +9,7 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DateDdmmyyyyPickerComponent } from '../date-ddmmyyyy-picker/date-ddmmyyyy-picker.component';
import {
FD_HOLDINGS,
GOVT_SECURITY_HOLDINGS,
@@ -16,6 +17,9 @@ import {
BOND_HOLDINGS,
ALL_TXNS,
SCRIPT_MASTER,
INTEREST_INCOME_GL_MASTER,
INTEREST_INCOME_GL_BY_TYPE,
CASH_BANK_GL_MASTER,
InvestmentHolding,
InvestmentTxn,
InstrumentType,
@@ -23,12 +27,13 @@ import {
INSTRUMENT_LABELS,
PROVISION_STATUS_LABELS,
accruedInterest,
accruedInterestBetween,
addPeriod,
daysToMaturity,
maturityAmount,
todayDDMMYYYY,
isNpi,
canAccrueInterest,
interestToPost,
interestReceivableBalance,
suspenseInterest,
provisionRequiredInv
@@ -67,7 +72,8 @@ type InterestTab =
standalone: true,
imports: [
CommonModule,
FormsModule
FormsModule,
DateDdmmyyyyPickerComponent
]
})
export class InvestmentInterestComponent {
@@ -83,6 +89,8 @@ export class InvestmentInterestComponent {
txns: InvestmentTxn[] = [...ALL_TXNS];
readonly scriptOptions = SCRIPT_MASTER;
readonly interestIncomeGlOptions = INTEREST_INCOME_GL_MASTER;
readonly cashBankGlOptions = CASH_BANK_GL_MASTER;
/** Draft "amount to receive" per certificate, keyed by certNo, for the Interest Receive tab. */
receiveDrafts: Record<string, number | null> = {};
@@ -92,17 +100,24 @@ export class InvestmentInterestComponent {
toastVisible = false;
private toastTimer: ReturnType<typeof setTimeout> | undefined;
// 01 FD Interest Calculation
fdCalcDate = this.asOnDate;
// 01 FD Interest Calculation — posts interest accrued strictly within [fromDate, toDate]
fdCalcFromDate = addPeriod(this.asOnDate, 0, -3);
fdCalcToDate = this.asOnDate;
fdCalcCertNo = '';
fdCalcAll = false;
fdCalcGlCode = INTEREST_INCOME_GL_BY_TYPE.FD;
// 02 Govt./Bond Int. Calculation
bondCalcDate = this.asOnDate;
// 02 Govt./Bond Int. Calculation — posts interest accrued strictly within [fromDate, toDate]
bondCalcFromDate = addPeriod(this.asOnDate, 0, -3);
bondCalcToDate = this.asOnDate;
bondCalcType: InstrumentType | '' = '';
bondCalcScript = '';
bondCalcDealNo = '';
bondCalcAll = false;
bondCalcGlCode = INTEREST_INCOME_GL_BY_TYPE.GOVT_SECURITY;
// Interest Receive — Cash/Bank GL credited for the day's collection voucher
receiveGlCode = CASH_BANK_GL_MASTER[0].glCode;
// Register filters
registerFromDate = '';
@@ -184,38 +199,56 @@ export class InvestmentInterestComponent {
}
fdInterest(h: InvestmentHolding): number {
return interestToPost(h, this.fdCalcDate || this.asOnDate);
return accruedInterestBetween(h, this.fdCalcFromDate || h.purchaseDate, this.fdCalcToDate || this.asOnDate);
}
get fdCalcTotal(): number {
return this.fdCalcTargets.reduce((s, h) => s + this.fdInterest(h), 0);
}
glName(glCode: string): string {
return (
this.interestIncomeGlOptions.find((g) => g.glCode === glCode)?.glName ??
this.cashBankGlOptions.find((g) => g.glCode === glCode)?.glName ??
glCode
);
}
processFdInterest(): void {
const targets = this.fdCalcTargets.filter((h) => this.fdInterest(h) > 0);
if (targets.length === 0) {
this.toast('No FD holdings have interest due to be posted.');
this.toast('No FD holdings have interest due to be posted for this period.');
return;
}
const toDate = this.fdCalcToDate || this.asOnDate;
const fromDate = this.fdCalcFromDate || toDate;
const glCode = this.fdCalcGlCode;
const glName = this.glName(glCode);
const newTxns: InvestmentTxn[] = targets.map((h, i) => {
const amt = this.fdInterest(h);
h.interestPosted = this.posted(h) + amt;
h.interestPostedTillDate = toDate;
return {
certNo: h.certNo,
instrumentType: 'FD',
txnType: 'INTEREST',
voucherNo: `INV/FD-INT/${(this.fdCalcDate || this.asOnDate).replace(/-/g, '')}-${i + 1}`,
txnDate: this.fdCalcDate || this.asOnDate,
voucherNo: `INV/FD-INT/${toDate.replace(/-/g, '')}-${i + 1}`,
txnDate: toDate,
amount: amt,
particulars: `FD interest posted (accrual) - ${h.party}`
particulars: `FD interest posted (accrual) for period ${fromDate} to ${toDate} - ${h.party}`,
glCode,
glName
};
});
this.fdCalcFromDate = toDate;
const total = newTxns.reduce((s, t) => s + t.amount, 0);
this.txns = [
...newTxns,
...this.txns
];
this.toast(`FD interest posted for ${targets.length} certificate(s). Total ₹${total.toLocaleString('en-IN')}.`);
this.toast(
`FD interest posted for ${targets.length} certificate(s) to ${glCode} - ${glName}. Total ₹${total.toLocaleString('en-IN')}.`
);
}
// -------------------- 02 Govt./Bond Int. Calculation --------------------
@@ -239,30 +272,44 @@ export class InvestmentInterestComponent {
}
bondInterest(h: InvestmentHolding): number {
return interestToPost(h, this.bondCalcDate || this.asOnDate);
return accruedInterestBetween(h, this.bondCalcFromDate || h.purchaseDate, this.bondCalcToDate || this.asOnDate);
}
get bondCalcTotal(): number {
return this.bondCalcTargets.reduce((s, h) => s + this.bondInterest(h), 0);
}
/** Re-suggests the Interest Income GL when the Type filter changes — still overridable. */
onBondCalcTypeChange(): void {
if (this.bondCalcType) {
this.bondCalcGlCode = INTEREST_INCOME_GL_BY_TYPE[this.bondCalcType];
}
}
processBondInterest(): void {
const targets = this.bondCalcTargets.filter((h) => this.bondInterest(h) > 0);
if (targets.length === 0) {
this.toast('No Govt/Bond holdings have interest due to be posted.');
this.toast('No Govt/Bond holdings have interest due to be posted for this period.');
return;
}
const toDate = this.bondCalcToDate || this.asOnDate;
const fromDate = this.bondCalcFromDate || toDate;
const glCode = this.bondCalcGlCode;
const glName = this.glName(glCode);
const newTxns: InvestmentTxn[] = targets.map((h, i) => {
const amt = this.bondInterest(h);
h.interestPosted = this.posted(h) + amt;
h.interestPostedTillDate = toDate;
return {
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,
voucherNo: `INV/BOND-INT/${toDate.replace(/-/g, '')}-${i + 1}`,
txnDate: toDate,
amount: amt,
particulars: `Coupon/interest posted (accrual) - ${h.scriptName}`
particulars: `Coupon/interest posted (accrual) for period ${fromDate} to ${toDate} - ${h.scriptName}`,
glCode,
glName
};
});
const total = newTxns.reduce((s, t) => s + t.amount, 0);
@@ -270,7 +317,10 @@ export class InvestmentInterestComponent {
...newTxns,
...this.txns
];
this.toast(`Interest posted for ${targets.length} holding(s). Total ₹${total.toLocaleString('en-IN')}.`);
this.bondCalcFromDate = toDate;
this.toast(
`Interest posted for ${targets.length} holding(s) to ${glCode} - ${glName}. Total ₹${total.toLocaleString('en-IN')}.`
);
}
// -------------------- Interest Receivable (all interest-bearing types) --------------------
@@ -331,6 +381,8 @@ export class InvestmentInterestComponent {
h.interestReceived = this.received(h) + amt;
particulars = `Interest received on cash basis (${this.provisionLabel(h)}) - ${h.scriptName || h.party}`;
}
const glCode = this.receiveGlCode;
const glName = this.glName(glCode);
this.txns = [
{
certNo: h.certNo,
@@ -339,11 +391,13 @@ export class InvestmentInterestComponent {
voucherNo: `INV/INT-RCV/${h.certNo}-${this.receivedLog.length + 1}`,
txnDate: this.asOnDate,
amount: amt,
particulars
particulars: `${particulars} (Dr. ${glCode} - ${glName})`,
glCode,
glName
},
...this.txns
];
this.toast(`₹${amt.toLocaleString('en-IN')} interest received for ${h.certNo}.`);
this.toast(`₹${amt.toLocaleString('en-IN')} interest received into ${glCode} - ${glName} for ${h.certNo}.`);
delete this.receiveDrafts[h.certNo];
}
@@ -400,7 +454,9 @@ export class InvestmentInterestComponent {
t.certNo,
t.instrumentType,
t.particulars,
t.amount
t.amount,
t.glCode ?? '',
t.glName ?? ''
];
}
@@ -412,7 +468,9 @@ export class InvestmentInterestComponent {
'Cert No',
'Type',
'Particulars',
'Amount'
'Amount',
'GL Code',
'GL Name'
];
const txnWidths = [
14,
@@ -420,7 +478,9 @@ export class InvestmentInterestComponent {
14,
14,
42,
16
16,
12,
28
];
const one = (
sheetName: string,
@@ -501,72 +561,78 @@ export class InvestmentInterestComponent {
return sheets;
}
case 'fdCalc':
return one(
'FD Interest Calc',
'FD Interest Calculation & Post',
[
'Cert No',
'Party',
'Face Value',
'Rate',
'Frequency',
'Interest'
],
[
14,
30,
16,
10,
14,
16
],
this.fdCalcTargets.map((h) => [
h.certNo,
h.party,
h.faceValue,
h.interestRate,
h.interestFreq,
this.fdInterest(h)
])
);
return [
{
sheetName: 'FD Interest Calc',
title: 'FD Interest Calculation & Post',
subtitle: `Period ${this.fdCalcFromDate || '—'} to ${this.fdCalcToDate || this.asOnDate} · Post to ${this.fdCalcGlCode} - ${this.glName(this.fdCalcGlCode)}`,
headers: [
'Cert No',
'Party',
'Face Value',
'Rate',
'Frequency',
'Interest'
],
widths: [
14,
30,
16,
10,
14,
16
],
rows: this.fdCalcTargets.map((h) => [
h.certNo,
h.party,
h.faceValue,
h.interestRate,
h.interestFreq,
this.fdInterest(h)
])
}
];
case 'bondCalc':
return one(
'Bond Interest Calc',
'Govt./Bond Interest Calculation & Post',
[
'Cert No',
'Script',
'Type',
'Face Value',
'Rate',
'Frequency',
'Interest'
],
[
14,
30,
16,
16,
10,
14,
16
],
this.bondCalcTargets.map((h) => [
h.certNo,
h.scriptName,
this.instrumentLabel(h.instrumentType),
h.faceValue,
h.interestRate,
h.interestFreq,
this.bondInterest(h)
])
);
return [
{
sheetName: 'Bond Interest Calc',
title: 'Govt./Bond Interest Calculation & Post',
subtitle: `Period ${this.bondCalcFromDate || '—'} to ${this.bondCalcToDate || this.asOnDate} · Post to ${this.bondCalcGlCode} - ${this.glName(this.bondCalcGlCode)}`,
headers: [
'Cert No',
'Script',
'Type',
'Face Value',
'Rate',
'Frequency',
'Interest'
],
widths: [
14,
30,
16,
16,
10,
14,
16
],
rows: this.bondCalcTargets.map((h) => [
h.certNo,
h.scriptName,
this.instrumentLabel(h.instrumentType),
h.faceValue,
h.interestRate,
h.interestFreq,
this.bondInterest(h)
])
}
];
case 'receive': {
const sheets: ExportSheetSpec[] = [
{
sheetName: 'Receive Targets',
title: 'Interest Receive — Outstanding for Collection',
subtitle,
subtitle: `${subtitle} · Receive into ${this.receiveGlCode} - ${this.glName(this.receiveGlCode)}`,
headers: [
'Cert No',
'Script / Party',
@@ -267,8 +267,8 @@
><span>Contact No</span><input type="text" [(ngModel)]="newBroker.contactNo"
/></label>
<label class="inv-form-field"
><span>Empanelled On</span><input type="text" [(ngModel)]="newBroker.empanelledOn" placeholder="DD-MM-YYYY"
/></label>
><span>Empanelled On</span><mifosx-date-ddmmyyyy [(ngModel)]="newBroker.empanelledOn"></mifosx-date-ddmmyyyy
></label>
</div>
<div class="inv-form-actions">
<button type="button" class="pg-btn pg-btn--primary" (click)="addBroker()">Save</button>
@@ -349,8 +349,8 @@
<div class="pos-section-label">Add Daily Script Rate</div>
<div class="inv-form-grid">
<label class="inv-form-field"
><span>Rate Date *</span><input type="text" [(ngModel)]="newDailyRate.rateDate" placeholder="DD-MM-YYYY"
/></label>
><span>Rate Date *</span><mifosx-date-ddmmyyyy [(ngModel)]="newDailyRate.rateDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field">
<span>Script Code *</span>
<select [(ngModel)]="newDailyRate.scriptCode">
@@ -9,6 +9,7 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DateDdmmyyyyPickerComponent } from '../date-ddmmyyyy-picker/date-ddmmyyyy-picker.component';
import {
SCRIPT_MASTER,
BROKER_MASTER,
@@ -60,7 +61,8 @@ type MasterTab =
standalone: true,
imports: [
CommonModule,
FormsModule
FormsModule,
DateDdmmyyyyPickerComponent
]
})
export class InvestmentMastersComponent {
@@ -138,8 +138,8 @@
/></label>
<label class="inv-form-field"
><span>Purchase Date *</span
><input type="text" [(ngModel)]="purchaseForm.purchaseDate" placeholder="DD-MM-YYYY"
/></label>
><mifosx-date-ddmmyyyy [(ngModel)]="purchaseForm.purchaseDate"></mifosx-date-ddmmyyyy
></label>
<label class="inv-form-field"
><span>Broker Code</span><input type="text" [(ngModel)]="purchaseForm.brokerCode"
/></label>
@@ -9,6 +9,7 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DateDdmmyyyyPickerComponent } from '../date-ddmmyyyy-picker/date-ddmmyyyy-picker.component';
import { SHARE_HOLDINGS, ALL_TXNS, InvestmentHolding, InvestmentTxn, todayDDMMYYYY } from '../investment-data';
import { exportWorkbook } from '../investment-export';
@@ -41,7 +42,8 @@ interface SharePurchaseForm {
standalone: true,
imports: [
CommonModule,
FormsModule
FormsModule,
DateDdmmyyyyPickerComponent
]
})
export class ShareInvestmentComponent {