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
@@ -17,7 +17,6 @@ import {
MUTUAL_FUND_PRODUCTS,
SGL_BANK_MASTER,
SCRIPT_MARKET_DETAIL,
DAILY_SCRIPT_RATES,
LIEN_REASON_MASTER,
ScriptMaster,
BrokerMaster,
@@ -36,7 +35,7 @@ import {
periodBetween
} from '../investment-data';
import { exportWorkbook, ExportSheetSpec } from '../investment-export';
import { InvestmentGlAccount, InvestmentService, apiErrorMessage } from '../investment.service';
import { InvestmentGlAccount, InvestmentService, apiErrorMessage, toDailyScriptRate } from '../investment.service';
type MasterTab =
| 'party'
@@ -79,7 +78,8 @@ export class InvestmentMastersComponent implements OnInit {
mfProducts: MutualFundProductMaster[] = [...MUTUAL_FUND_PRODUCTS];
sglBanks: SglBankMaster[] = [...SGL_BANK_MASTER];
marketDetails: ScriptMarketDetail[] = [...SCRIPT_MARKET_DETAIL];
dailyScriptRates: DailyScriptRate[] = [...DAILY_SCRIPT_RATES];
/** Loaded from the backend (Daily Script Master) in ngOnInit. */
dailyScriptRates: DailyScriptRate[] = [];
lienReasons: LienReason[] = [...LIEN_REASON_MASTER];
readonly instrumentTypes: InstrumentType[] = [
@@ -131,6 +131,7 @@ export class InvestmentMastersComponent implements OnInit {
newDailyRate: Partial<DailyScriptRate> = {};
ngOnInit(): void {
this.loadDailyRates();
this.investmentService.getAssetGlAccounts().subscribe({
next: (gls) => (this.investmentGlOptions = gls),
error: (err) => this.toast(`Could not load GL accounts: ${apiErrorMessage(err)}`)
@@ -435,24 +436,56 @@ export class InvestmentMastersComponent implements OnInit {
this.toast('SGL Bank master added.');
}
private loadDailyRates(): void {
this.investmentService.getDailyRates().subscribe({
next: (rows) => (this.dailyScriptRates = rows.map(toDailyScriptRate)),
error: (err) => this.toast(`Could not load daily script rates: ${apiErrorMessage(err)}`)
});
}
addDailyRate(): void {
if (!this.newDailyRate.scriptCode || !this.newDailyRate.rateDate || this.newDailyRate.closeRate == null) {
const r = this.newDailyRate;
if (!r.scriptCode || !r.rateDate || r.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.');
const script = this.scripts.find((s) => s.scriptCode === r.scriptCode);
if (!script?.id) {
this.toast(`Script ${r.scriptCode} is not saved in Script Master.`);
return;
}
const body = {
scriptId: script.id,
rateDate: r.rateDate,
closeRate: r.closeRate,
...(r.openRate != null ? { openRate: r.openRate } : {}),
...(r.highRate != null ? { highRate: r.highRate } : {}),
...(r.lowRate != null ? { lowRate: r.lowRate } : {}),
dateFormat: 'dd-MM-yyyy',
locale: 'en'
};
this.investmentService.createDailyRate(body).subscribe({
next: () => {
this.toast(`Rate for ${r.scriptCode} on ${r.rateDate} saved.`);
this.newDailyRate = {};
this.loadDailyRates();
},
error: (err) => this.toast(apiErrorMessage(err))
});
}
/** A wrong rate is corrected by dropping it and entering it again. */
dropDailyRate(r: DailyScriptRate): void {
if (!r.id) {
return;
}
this.investmentService.deleteDailyRate(r.id).subscribe({
next: () => {
this.toast(`Rate for ${r.scriptCode} on ${r.rateDate} dropped.`);
this.loadDailyRates();
},
error: (err) => this.toast(apiErrorMessage(err))
});
}
// -------------------- Export to Excel --------------------