Files
cbsnexora/src/app/investment/investment-masters/investment-masters.component.ts
T
niteshandClaude Sonnet 5 ebe67cc410 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>
2026-08-02 19:26:19 +05:30

518 lines
13 KiB
TypeScript

/**
* 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 { DateDdmmyyyyPickerComponent } from '../date-ddmmyyyy-picker/date-ddmmyyyy-picker.component';
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';
import { exportWorkbook, ExportSheetSpec } from '../investment-export';
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,
DateDdmmyyyyPickerComponent
]
})
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'
];
private readonly masterLabels: Record<MasterTab, string> = {
party: 'Bank / Party Master',
script: 'Script Master',
subtype: 'SubType Master',
broker: 'Broker Master',
control: 'Control Parameter',
dailyscript: 'Daily Script Master',
lien: 'LIEN Reason Master',
mfproduct: 'Mutual Fund Product Master',
sglbank: 'SGL Bank Master',
marketdetail: 'Script Market Detail'
};
generating = false;
toastMsg = '';
toastVisible = false;
private toastTimer: ReturnType<typeof setTimeout> | undefined;
newParty: Partial<PartyMaster> = {};
newScript: Partial<ScriptMaster> = {};
newSubType: Partial<SubTypeMaster> = {};
newBroker: Partial<BrokerMaster> = {};
newControlParam: Partial<ControlParameter> = {};
newLienReason: Partial<LienReason> = {};
newMfProduct: Partial<MutualFundProductMaster> = {};
newSglBank: Partial<SglBankMaster> = {};
newDailyRate: Partial<DailyScriptRate> = {};
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.');
}
// -------------------- Export to Excel --------------------
private exportSheets(): ExportSheetSpec[] {
const title = this.masterLabels[this.activeTab];
const one = (headers: string[], widths: number[], rows: (string | number | null)[][]): ExportSheetSpec[] => [
{ sheetName: title, title, headers, widths, rows }];
switch (this.activeTab) {
case 'party':
return one(
[
'Party Code',
'Party Name',
'Type',
'PAN No'
],
[
12,
30,
16,
14
],
this.parties.map((p) => [
p.partyCode,
p.partyName,
p.partyType,
p.panNo
])
);
case 'script':
return one(
[
'Script Code',
'Script Name',
'Instrument',
'ISIN',
'Face Value/Unit'
],
[
16,
30,
16,
16,
16
],
this.scripts.map((s) => [
s.scriptCode,
s.scriptName,
s.instrumentType,
s.isin,
s.faceValuePerUnit
])
);
case 'subtype':
return one(
[
'Code',
'Sub Type',
'Instrument'
],
[
10,
24,
16
],
this.subTypes.map((s) => [
s.subTypeCode,
s.subTypeName,
s.instrumentType
])
);
case 'broker':
return one(
[
'Broker Code',
'Broker Name',
'Contact',
'Empanelled On',
'Status'
],
[
14,
30,
16,
16,
12
],
this.brokers.map((b) => [
b.brokerCode,
b.brokerName,
b.contactNo,
b.empanelledOn,
b.status
])
);
case 'control':
return one(
[
'Parameter',
'Value',
'Description'
],
[
26,
14,
40
],
this.controlParams.map((c) => [
c.paramName,
c.paramValue,
c.description
])
);
case 'dailyscript':
return one(
[
'Rate Date',
'Script Code',
'Script Name',
'Open',
'High',
'Low',
'Close'
],
[
14,
16,
26,
12,
12,
12,
12
],
this.dailyScriptRates.map((r) => [
r.rateDate,
r.scriptCode,
r.scriptName,
r.openRate,
r.highRate,
r.lowRate,
r.closeRate
])
);
case 'lien':
return one(
[
'Reason Code',
'Description'
],
[
14,
40
],
this.lienReasons.map((l) => [
l.reasonCode,
l.reasonDescription
])
);
case 'mfproduct':
return one(
[
'Product Code',
'Product Name',
'AMC',
'Category',
'Risk'
],
[
14,
30,
24,
20,
12
],
this.mfProducts.map((m) => [
m.productCode,
m.productName,
m.amcName,
m.category,
m.riskLevel
])
);
case 'sglbank':
return one(
[
'SGL Code',
'Bank Name',
'Branch'
],
[
14,
30,
24
],
this.sglBanks.map((s) => [
s.sglCode,
s.bankName,
s.branch
])
);
case 'marketdetail':
return one(
[
'Script Code',
'Market Price',
'As On',
'YTM %'
],
[
16,
16,
14,
12
],
this.marketDetails.map((m) => [
m.scriptCode,
m.marketPrice,
m.asOnDate,
m.ytm
])
);
default:
return one([], [], []);
}
}
async exportExcel(): Promise<void> {
this.generating = true;
try {
await exportWorkbook(this.exportSheets(), `Investment-${this.masterLabels[this.activeTab]}`);
this.toast(`${this.masterLabels[this.activeTab]} exported.`);
} catch (err) {
console.error('Investment Masters export failed', err);
this.toast('Could not generate the workbook — please retry.');
} finally {
this.generating = false;
}
}
private toast(msg: string): void {
this.toastMsg = msg;
this.toastVisible = true;
if (this.toastTimer) {
clearTimeout(this.toastTimer);
}
this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
}
}