Bucket Wise Investment
diff --git a/src/app/investment/bond-investment/bond-investment.component.html b/src/app/investment/bond-investment/bond-investment.component.html
index f8f5c5875..492ab3f53 100644
--- a/src/app/investment/bond-investment/bond-investment.component.html
+++ b/src/app/investment/bond-investment/bond-investment.component.html
@@ -336,6 +336,19 @@
+ @if (purchaseIsRated) {
+
+ }
@if (!isMutualFund) {
-
+
+ @if (!purchaseIsRated) {
+
+ }
+ @if (scriptIsRated) {
+ Credit Rating (RBI: bonds "A" or higher, CP / CD "A2" or higher)
+
+
+ * Rating
+
+
+
+ Rating Agency
+
+
+
+ Listing
+
+
+
+ @if (scriptForm.rating) {
+
+ {{
+ scriptRatingEligible
+ ? 'Rating meets the RBI minimum; this security can be bought.'
+ : 'Below the RBI minimum rating; a purchase of this security will be blocked.'
+ }}
+
+ }
+ }
Company Details (Bond / Mutual Fund)
Int Rate %
| Issue Date |
Maturity |
+ Rating |
Status |
@@ -398,6 +445,7 @@
{{ s.couponRate ?? '—' }} |
{{ s.issueDate || '—' }} |
{{ s.maturityDate || '—' }} |
+ {{ s.rating ? s.rating + (s.listed === false ? ' · Unlisted' : '') : '—' }} |
{{ s.active === false ? 'INACTIVE' : 'ACTIVE' }} |
}
diff --git a/src/app/investment/investment-masters/investment-masters.component.ts b/src/app/investment/investment-masters/investment-masters.component.ts
index a707698f7..13c2f137c 100644
--- a/src/app/investment/investment-masters/investment-masters.component.ts
+++ b/src/app/investment/investment-masters/investment-masters.component.ts
@@ -32,6 +32,9 @@ import {
BANK_TYPE_OPTIONS,
SLR_TYPE_OPTIONS,
INVESTMENT_GL_LOOKUP,
+ ELIGIBLE_RATINGS,
+ RATING_AGENCY_OPTIONS,
+ RATING_OPTIONS,
periodBetween
} from '../investment-data';
import { exportWorkbook, ExportSheetSpec } from '../investment-export';
@@ -124,6 +127,8 @@ export class InvestmentMastersComponent implements OnInit {
saving = false;
readonly bankTypeOptions = BANK_TYPE_OPTIONS;
readonly slrTypeOptions = SLR_TYPE_OPTIONS;
+ readonly ratingOptions = RATING_OPTIONS;
+ readonly ratingAgencyOptions = RATING_AGENCY_OPTIONS;
/** Investment GLs (under the 100700 INVESTMENTS head) a party or script can be mapped to — not loans, banks or test GLs. */
investmentGlOptions: InvestmentGlAccount[] = [];
/** Income GLs under 400400 INCOME FROM INVESTMENTS, for an FD bank's interest. */
@@ -295,6 +300,19 @@ export class InvestmentMastersComponent implements OnInit {
].includes(this.scriptForm.instrumentType ?? '');
}
+ /** Corporate bonds, CPs and CDs carry a credit rating and a listed / unlisted flag (RBI non-SLR rules). */
+ get scriptIsRated(): boolean {
+ return [
+ 'NON_SLR',
+ 'BOND'
+ ].includes(this.scriptForm.instrumentType ?? '');
+ }
+
+ /** Whether the rating entered meets the RBI minimum: "A" for bonds, "A2" for CP / CD. */
+ get scriptRatingEligible(): boolean {
+ return !!this.scriptForm.rating && ELIGIBLE_RATINGS.includes(this.scriptForm.rating);
+ }
+
/** Preview of the code the backend will assign — a type letter + running number, e.g. G00001. */
get nextScriptCode(): string {
const prefixByType: Record = {
@@ -332,6 +350,10 @@ export class InvestmentMastersComponent implements OnInit {
this.toast('Enter ISIN, Int Rate, Issue Date and Maturity Date for this security.');
return;
}
+ if (this.scriptIsRated && !f.rating) {
+ this.toast('Enter the credit rating of this bond / CP / CD.');
+ return;
+ }
const body = {
bondType: f.instrumentType,
slrType: f.slrType ?? '',
@@ -350,6 +372,9 @@ export class InvestmentMastersComponent implements OnInit {
phone: f.phone ?? '',
fax: f.fax ?? '',
corpCode: f.corpCode ?? '',
+ rating: this.scriptIsRated ? (f.rating ?? '') : '',
+ ratingAgency: this.scriptIsRated ? (f.ratingAgency ?? '') : '',
+ listed: !this.scriptIsRated || f.listed !== false,
active: f.active !== false,
dateFormat: 'dd-MM-yyyy',
locale: 'en'
diff --git a/src/app/investment/investment-routing.module.ts b/src/app/investment/investment-routing.module.ts
index f79755069..bb9cc93a7 100644
--- a/src/app/investment/investment-routing.module.ts
+++ b/src/app/investment/investment-routing.module.ts
@@ -19,6 +19,7 @@ import { InvestmentReportsComponent } from './investment-reports/investment-repo
import { InvestmentInterestComponent } from './investment-interest/investment-interest.component';
import { InvestmentDiaryComponent } from './investment-diary/investment-diary.component';
import { InvestmentInterestReportComponent } from './investment-interest-report/investment-interest-report.component';
+import { InvestmentBucketReportComponent } from './investment-bucket-report/investment-bucket-report.component';
const routes: Routes = [
Route.withShell([
@@ -66,6 +67,11 @@ const routes: Routes = [
component: InvestmentInterestReportComponent,
data: { title: 'Interest Received & Receivable', breadcrumb: 'Interest Received & Receivable' }
},
+ {
+ path: 'bucket-report',
+ component: InvestmentBucketReportComponent,
+ data: { title: 'Bucket Wise Investment', breadcrumb: 'Bucket Wise Investment' }
+ },
{
path: 'reports',
component: InvestmentReportsComponent,
diff --git a/src/app/investment/investment-shared.scss b/src/app/investment/investment-shared.scss
index 9945581f3..0d9fc1318 100644
--- a/src/app/investment/investment-shared.scss
+++ b/src/app/investment/investment-shared.scss
@@ -260,3 +260,14 @@
transform: translateX(-50%) translateY(0);
}
}
+
+.inv-hint {
+ margin: 8px 0;
+ padding: 0 16px;
+ font-size: 13px;
+ color: #15803d;
+
+ &--bad {
+ color: #b91c1c;
+ }
+}
diff --git a/src/app/investment/investment.service.ts b/src/app/investment/investment.service.ts
index e702425c8..3ca835bc8 100644
--- a/src/app/investment/investment.service.ts
+++ b/src/app/investment/investment.service.ts
@@ -169,6 +169,44 @@ export interface InvestmentInterestReportDto {
rows: InvestmentInterestReportRowDto[];
}
+/** One maturity bucket of the Bucket Wise Investment report, or its total. */
+export interface InvestmentBucketDto {
+ code: string;
+ label: string;
+ govtCount: number;
+ govtFaceValue: number;
+ govtBookValue: number;
+ bondCount: number;
+ bondFaceValue: number;
+ bondBookValue: number;
+ fdCount: number;
+ fdPrincipal: number;
+ totalAmount: number;
+}
+
+/** One Govt Security, bond or FD held on the as-on date, with the bucket it falls in. */
+export interface InvestmentBucketRowDto {
+ bucket: string;
+ instrument: 'GOVT_SECURITY' | 'BOND' | 'FD';
+ refId: number;
+ dealNo: string;
+ particulars: string;
+ category?: string;
+ rate?: number;
+ maturityDate: string;
+ daysToMaturity: number;
+ faceValue: number;
+ bookValue: number;
+}
+
+/** Holdings on a date split by the time left to maturity. */
+export interface InvestmentBucketReportDto {
+ asOnDate: string;
+ buckets: InvestmentBucketDto[];
+ total: InvestmentBucketDto;
+ rows: InvestmentBucketRowDto[];
+}
+
/** One dated event in the Due Date Diary. */
export interface InvestmentDiaryRowDto {
date: string;
@@ -250,9 +288,29 @@ export interface InvestmentScriptDto {
phone?: string;
fax?: string;
corpCode?: string;
+ rating?: string;
+ ratingAgency?: string;
+ listed: boolean;
active: boolean;
}
+/** Where a non-SLR purchase stands against the RBI limits, from /investment/purchases/non-slr-limit. */
+export interface InvestmentNonSlrLimitDto {
+ asOnDate: string;
+ totalDeposits?: number;
+ nonSlrLimit?: number;
+ nonSlrOutstanding: number;
+ nonSlrAfter: number;
+ nonSlrBase: number;
+ unlistedLimit: number;
+ unlistedOutstanding: number;
+ unlistedAfter: number;
+ newAmount: number;
+ listed: boolean;
+ withinLimits: boolean;
+ message: string;
+}
+
/** Purchase entry (holding) as returned by /investment/purchases (dates are DD-MM-YYYY). */
export interface InvestmentPurchaseDto {
id: number;
@@ -468,6 +526,15 @@ export class InvestmentService {
);
}
+ /** RBI non-SLR limits for a purchase of `amount` (book value) in a script on a deal date (DD-MM-YYYY). */
+ getNonSlrLimit(scriptId: number | null, dealDate: string, amount: number): Observable {
+ const params: Record = { dealDate, amount: String(amount || 0) };
+ if (scriptId) {
+ params['scriptId'] = String(scriptId);
+ }
+ return this.http.get('/investment/purchases/non-slr-limit', { params });
+ }
+
createPurchase(body: object): Observable<{ resourceId: number; resourceIdentifier: string }> {
return this.http.post<{ resourceId: number; resourceIdentifier: string }>('/investment/purchases', body);
}
@@ -532,6 +599,11 @@ export class InvestmentService {
return this.http.get('/investment/interest-report', { params: { fromDate, toDate } });
}
+ /** Bucket Wise Investment: holdings on a date (DD-MM-YYYY) split by the time left to maturity. */
+ getBucketReport(asOnDate: string): Observable {
+ return this.http.get('/investment/bucket-report', { params: { asOnDate } });
+ }
+
/** Due Date Diary: dated events of all active investments between two dates (DD-MM-YYYY). */
getDiary(fromDate: string, toDate: string): Observable {
return this.http.get('/investment/diary', { params: { fromDate, toDate } });
@@ -643,6 +715,9 @@ export function toScriptMaster(s: InvestmentScriptDto): ScriptMaster {
phone: s.phone,
fax: s.fax,
corpCode: s.corpCode,
+ rating: s.rating,
+ ratingAgency: s.ratingAgency,
+ listed: s.listed,
active: s.active
};
}