feat(investment): Daily Script Master, market rate on purchase, broken-period interest as expense

- 09 Daily Script Master now saves to and loads from the backend (sample
  rows removed), with Drop to correct a wrong rate.
- Purchase Entry takes the rate (NAV for a mutual fund) from the newest
  Daily Script Master rate on or before the deal date, shows which rate
  was used, warns when the rate entered is outside the day's low-high,
  and clears the rate when the script has none.
- Broken-period interest paid to the seller is debited to expense GL
  500606 instead of Interest Receivable (RBI UCB Investment Directions,
  para 152, confirmed by Jitendra Sir); Interest Entry labels it so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 15:27:15 +05:30
co-authored by Claude Opus 5.5
parent 88f34f58e3
commit 6a04a92113
6 changed files with 191 additions and 25 deletions
+49
View File
@@ -11,6 +11,7 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, forkJoin } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import {
DailyScriptRate,
InstrumentType,
InvestmentHolding,
PARTY_MASTER,
@@ -178,6 +179,27 @@ export class InvestmentService {
);
}
// -------------------- Daily Script Master --------------------
getDailyRates(scriptId?: number): Observable<InvestmentDailyRateDto[]> {
const params = scriptId ? `?scriptId=${scriptId}` : '';
return this.http.get<InvestmentDailyRateDto[]>(`/investment/daily-rates${params}`);
}
/** Newest Daily Script Master rate of a script on or before a date; null when none has been entered. */
getLatestDailyRate(scriptId: number, onDate: string): Observable<InvestmentDailyRateDto | null> {
return this.http.get<InvestmentDailyRateDto | null>(
`/investment/daily-rates/latest?scriptId=${scriptId}&onDate=${encodeURIComponent(onDate)}`
);
}
createDailyRate(body: object): Observable<{ resourceId: number }> {
return this.http.post<{ resourceId: number }>('/investment/daily-rates', body);
}
deleteDailyRate(rateId: number): Observable<unknown> {
return this.http.delete(`/investment/daily-rates/${rateId}`);
}
createPurchase(body: object): Observable<{ resourceId: number; resourceIdentifier: string }> {
return this.http.post<{ resourceId: number; resourceIdentifier: string }>('/investment/purchases', body);
}
@@ -337,6 +359,33 @@ export interface InvestmentInterestScheduleDto {
rows: InvestmentInterestRowDto[];
}
/** A Daily Script Master row (see InvestmentDailyRateData in the backend). */
export interface InvestmentDailyRateDto {
id: number;
scriptId: number;
scriptCode: string;
scriptDesc: string;
bondType: string;
rateDate: string;
openRate: number | null;
highRate: number | null;
lowRate: number | null;
closeRate: number;
}
export function toDailyScriptRate(d: InvestmentDailyRateDto): DailyScriptRate {
return {
id: d.id,
rateDate: d.rateDate,
scriptCode: d.scriptCode,
scriptName: d.scriptDesc,
openRate: d.openRate,
closeRate: d.closeRate,
highRate: d.highRate,
lowRate: d.lowRate
};
}
/** The first validation / domain-rule message from a Fineract error response, for a toast. */
export function apiErrorMessage(err: unknown): string {
const e = err as HttpErrorResponse;