- 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>
518 lines
13 KiB
TypeScript
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);
|
|
}
|
|
}
|