feat(investment): due date diary, script status, deal no by deal date, matured-only redemption, keep typed rate and business-rule error title
This commit is contained in:
@@ -664,10 +664,13 @@
|
||||
<span>* Deal No</span>
|
||||
<select [(ngModel)]="redeemPurchaseId" (ngModelChange)="onRedeemDealChange()">
|
||||
<option [ngValue]="null">-- Select --</option>
|
||||
@for (h of saleableHoldings; track h.certNo) {
|
||||
@for (h of redeemableHoldings; track h.certNo) {
|
||||
<option [ngValue]="h.id">{{ h.dealNo }} — {{ h.scriptName }} (matures {{ h.maturityDate }})</option>
|
||||
}
|
||||
</select>
|
||||
@if (!redeemableHoldings.length) {
|
||||
<small>No holding has reached its maturity date yet.</small>
|
||||
}
|
||||
</label>
|
||||
@if (redeemQuote; as q) {
|
||||
<label class="inv-form-field"
|
||||
|
||||
@@ -190,9 +190,10 @@ export class BondInvestmentComponent implements OnInit {
|
||||
|
||||
/** Script and party dropdowns offer the masters saved in the backend. */
|
||||
ngOnInit(): void {
|
||||
// The home-screen interest alert links here with ?tab=interest.
|
||||
if (this.route.snapshot.queryParamMap.get('tab') === 'interest') {
|
||||
this.activeTab = 'interest';
|
||||
// The home-screen alert and the Due Date Diary link here with ?tab=interest / redemption / amortize.
|
||||
const tab = this.route.snapshot.queryParamMap.get('tab');
|
||||
if (tab === 'interest' || tab === 'redemption' || tab === 'amortize') {
|
||||
this.activeTab = tab;
|
||||
}
|
||||
this.investmentService.syncSharedMasters().subscribe({
|
||||
error: (err) => this.toast(`Could not load Script / Party masters: ${apiErrorMessage(err)}`)
|
||||
@@ -470,9 +471,9 @@ export class BondInvestmentComponent implements OnInit {
|
||||
return INSTRUMENT_LABELS[type];
|
||||
}
|
||||
|
||||
/** Preview of the deal number the backend will assign: FY prefix (e.g. 202627) + running number. */
|
||||
private nextDealNo(): string {
|
||||
const d = parseDDMMYYYY(this.asOnDate);
|
||||
/** Preview of the deal number the backend will assign: FY of the deal date (e.g. 202627) + running number. */
|
||||
private nextDealNo(dealDate?: string): string {
|
||||
const d = parseDDMMYYYY(dealDate || this.asOnDate);
|
||||
const fyStart = d.getMonth() >= 3 ? d.getFullYear() : d.getFullYear() - 1;
|
||||
const prefix = `${fyStart}${String((fyStart + 1) % 100).padStart(2, '0')}`;
|
||||
const nums = (this.holdings ?? [])
|
||||
@@ -668,6 +669,11 @@ export class BondInvestmentComponent implements OnInit {
|
||||
|
||||
/** Re-suggests the last coupon date when the deal date changes. */
|
||||
onDealDateChange(): void {
|
||||
const f = this.purchaseForm;
|
||||
// A new deal takes its number from the deal date's financial year; a saved deal keeps its own.
|
||||
if (f.dealDate && !this.holdings.some((h) => h.dealNo === f.dealNo)) {
|
||||
f.dealNo = this.nextDealNo(f.dealDate);
|
||||
}
|
||||
this.suggestLastIntDate();
|
||||
this.loadMarketRate();
|
||||
}
|
||||
@@ -754,6 +760,9 @@ export class BondInvestmentComponent implements OnInit {
|
||||
*/
|
||||
purchaseMarketRate: InvestmentDailyRateDto | null | undefined;
|
||||
|
||||
/** The rate this screen last filled in from the Daily Script Master, to tell it apart from one the user typed. */
|
||||
private autoFilledRate: number | null = null;
|
||||
|
||||
private loadMarketRate(): void {
|
||||
const s = this.purchaseScript;
|
||||
const f = this.purchaseForm;
|
||||
@@ -764,12 +773,16 @@ export class BondInvestmentComponent implements OnInit {
|
||||
this.investmentService.getLatestDailyRate(s.id, f.dealDate).subscribe({
|
||||
next: (rate) => {
|
||||
this.purchaseMarketRate = rate;
|
||||
// No market rate: clear the field rather than leave another script's rate in it.
|
||||
const current = this.isMutualFund ? f.nav : f.purchaseRate;
|
||||
// A market rate fills the field. Without one, only a rate this screen filled in earlier (another script's or
|
||||
// another day's) is cleared; a rate the user typed stays.
|
||||
const next = rate ? rate.closeRate : current === this.autoFilledRate ? null : current;
|
||||
if (this.isMutualFund) {
|
||||
f.nav = rate ? rate.closeRate : null;
|
||||
f.nav = next;
|
||||
} else {
|
||||
f.purchaseRate = rate ? rate.closeRate : null;
|
||||
f.purchaseRate = next;
|
||||
}
|
||||
this.autoFilledRate = rate ? rate.closeRate : null;
|
||||
},
|
||||
error: (err) => this.toast(`Could not read the market rate: ${apiErrorMessage(err)}`)
|
||||
});
|
||||
@@ -1080,6 +1093,12 @@ export class BondInvestmentComponent implements OnInit {
|
||||
return this.activeHoldingsForType.filter((h) => !!h.id);
|
||||
}
|
||||
|
||||
/** Redemption is at maturity, so only holdings that have reached it are offered. */
|
||||
get redeemableHoldings(): InvestmentHolding[] {
|
||||
const today = parseDDMMYYYY(this.asOnDate);
|
||||
return this.saleableHoldings.filter((h) => !!h.maturityDate && parseDDMMYYYY(h.maturityDate) <= today);
|
||||
}
|
||||
|
||||
/** Shows what the redemption will post, or why it cannot be done yet (before maturity, coupon not posted). */
|
||||
onRedeemDealChange(): void {
|
||||
this.redeemQuote = null;
|
||||
|
||||
Reference in New Issue
Block a user