feat(investment): MTM valuation and mutual fund switch screens
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1552,60 +1552,204 @@
|
|||||||
<!-- ===================== 09 INVESTMENT DEPRECIATION ===================== -->
|
<!-- ===================== 09 INVESTMENT DEPRECIATION ===================== -->
|
||||||
@if (activeTab === 'depreciation') {
|
@if (activeTab === 'depreciation') {
|
||||||
<div class="pg-card">
|
<div class="pg-card">
|
||||||
<div class="pos-section-label">Investment - Investment Depreciation (Mark-to-Market Provisioning)</div>
|
<div class="pos-section-label">Investment - Investment Depreciation (Market Valuation of AFS / HFT)</div>
|
||||||
<div class="inv-form-grid">
|
<div class="inv-form-grid">
|
||||||
<label class="inv-form-field"
|
<label class="inv-form-field"
|
||||||
><span>Valuation Date</span><mifosx-date-ddmmyyyy [(ngModel)]="depreciationDate"></mifosx-date-ddmmyyyy
|
><span>* Valuation Date</span><mifosx-date-ddmmyyyy [(ngModel)]="depreciationDate"></mifosx-date-ddmmyyyy
|
||||||
></label>
|
></label>
|
||||||
<label class="inv-form-field">
|
<label class="inv-form-field">
|
||||||
<span>Category</span>
|
<span>* Category</span>
|
||||||
<select [(ngModel)]="depreciationCategory">
|
<select [(ngModel)]="depreciationCategory">
|
||||||
<option value="">-- All Categories --</option>
|
<option value="AFS">AFS (Available For Sale) — quarterly</option>
|
||||||
@for (c of categoryOptions; track c) {
|
<option value="HFT">HFT (Held For Trading) — monthly</option>
|
||||||
<option [value]="c">{{ c }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Holdings Requiring Provision</span><input type="text" [value]="depreciationTargets.length" readonly
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Total Depreciation</span
|
|
||||||
><input type="text" [value]="'₹' + (depreciationTotal | number: '1.2-2')" readonly
|
|
||||||
/></label>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="inv-hint">
|
||||||
|
Each security still held is valued at the latest Daily Script Master rate / NAV on or before the date. Gains and
|
||||||
|
losses are netted within Govt Securities, Corporate Bonds and Mutual Funds separately; a net loss is provided in
|
||||||
|
the Investment Depreciation Reserve and a net gain is ignored. The book value does not change. HTM is not
|
||||||
|
valued.
|
||||||
|
</p>
|
||||||
<div class="inv-form-actions">
|
<div class="inv-form-actions">
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="runDepreciation()">Process</button>
|
<button type="button" class="pg-btn pg-btn--primary" [disabled]="valuationLoading" (click)="previewValuation()">
|
||||||
|
{{ valuationLoading ? 'Valuing…' : 'Preview' }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="pg-btn pg-btn--primary"
|
||||||
|
[disabled]="!valuationPreview?.postable || valuationPosting"
|
||||||
|
(click)="postValuation()"
|
||||||
|
>
|
||||||
|
Post
|
||||||
|
</button>
|
||||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="setTab('holdings')">Exit</button>
|
<button type="button" class="pg-btn pg-btn--ghost" (click)="setTab('holdings')">Exit</button>
|
||||||
</div>
|
</div>
|
||||||
|
@if (valuationError) {
|
||||||
|
<p class="pg-sub" style="padding: 0 16px; color: #b42318">{{ valuationError }}</p>
|
||||||
|
}
|
||||||
|
@if (valuationPreview; as v) {
|
||||||
|
@if (v.missingRates.length) {
|
||||||
|
<p class="inv-hint inv-hint--bad">
|
||||||
|
No rate on or before {{ v.valuationDate }} for: {{ v.missingRates.join(', ') }}. Enter it in Daily Script
|
||||||
|
Master (09) first.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
@if (v.lastRunDate && !v.postable && !v.missingRates.length) {
|
||||||
|
<p class="inv-hint inv-hint--bad">
|
||||||
|
The last {{ v.category }} valuation is dated {{ v.lastRunDate }}; a new one must be dated after it.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
<table class="pg-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Class</th>
|
||||||
|
<th class="num">Book Value</th>
|
||||||
|
<th class="num">Market Value</th>
|
||||||
|
<th class="num">Net Gain / Loss</th>
|
||||||
|
<th class="num col-out">IDR Needed</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (c of v.classes; track c.assetClass) {
|
||||||
|
<tr>
|
||||||
|
<td>{{ valuationClassLabels[c.assetClass] }}</td>
|
||||||
|
<td class="num">₹{{ c.bookValue | number: '1.2-2' }}</td>
|
||||||
|
<td class="num">₹{{ c.marketValue | number: '1.2-2' }}</td>
|
||||||
|
<td class="num">₹{{ c.netDifference | number: '1.2-2' }}</td>
|
||||||
|
<td class="num col-out">₹{{ c.required | number: '1.2-2' }}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p class="pg-sub" style="padding: 0 16px">
|
||||||
|
IDR needed ₹{{ v.requiredTotal | number: '1.2-2' }} · held from the last run ₹{{
|
||||||
|
v.previousTotal | number: '1.2-2'
|
||||||
|
}}
|
||||||
|
·
|
||||||
|
@if (v.delta > 0) {
|
||||||
|
Voucher dated {{ v.valuationDate }}: Dr {{ v.provisionGlCode }} Provision for Depreciation / Cr
|
||||||
|
{{ v.reserveGlCode }} IDR ₹{{ v.delta | number: '1.2-2' }}
|
||||||
|
} @else if (v.delta < 0) {
|
||||||
|
Write-back dated {{ v.valuationDate }}: Dr {{ v.reserveGlCode }} IDR / Cr {{ v.provisionGlCode }} Provision
|
||||||
|
for Depreciation ₹{{ -v.delta | number: '1.2-2' }}
|
||||||
|
} @else {
|
||||||
|
No change in the IDR, so no voucher (the valuation is still recorded).
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
<table class="pg-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Deal No</th>
|
||||||
|
<th>Script</th>
|
||||||
|
<th>Class</th>
|
||||||
|
<th class="num">Units</th>
|
||||||
|
<th class="num">Book Value</th>
|
||||||
|
<th class="num">Rate / NAV</th>
|
||||||
|
<th>Rate Date</th>
|
||||||
|
<th class="num">Market Value</th>
|
||||||
|
<th class="num">Gain / Loss</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (l of v.lines; track l.purchaseId) {
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="pg-chip">{{ l.dealNo }}</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ l.scriptCode }} {{ l.scriptDesc }}</td>
|
||||||
|
<td>{{ valuationClassLabels[l.assetClass] }}</td>
|
||||||
|
<td class="num">{{ l.units | number: '1.0-6' }}</td>
|
||||||
|
<td class="num">₹{{ l.bookValue | number: '1.2-2' }}</td>
|
||||||
|
<td class="num">{{ l.rate | number: '1.2-4' }}</td>
|
||||||
|
<td>{{ l.rateDate }}</td>
|
||||||
|
<td class="num">₹{{ l.marketValue | number: '1.2-2' }}</td>
|
||||||
|
<td class="num" [class.col-out]="l.difference < 0">₹{{ l.difference | number: '1.2-2' }}</td>
|
||||||
|
</tr>
|
||||||
|
} @empty {
|
||||||
|
<tr>
|
||||||
|
<td colspan="9" class="pg-empty">No {{ v.category }} holding with a rate on {{ v.valuationDate }}.</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="pg-card">
|
||||||
|
<div class="pos-section-label">Valuation Runs</div>
|
||||||
<table class="pg-table">
|
<table class="pg-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Cert No</th>
|
<th>Date</th>
|
||||||
<th>Script</th>
|
<th>Category</th>
|
||||||
<th class="num">Book Rate</th>
|
<th class="num">Govt Securities</th>
|
||||||
<th class="num">Market Price</th>
|
<th class="num">Corporate Bonds</th>
|
||||||
<th class="num col-out">Depreciation</th>
|
<th class="num">Mutual Funds</th>
|
||||||
|
<th class="num">IDR Needed</th>
|
||||||
|
<th class="num">Posted</th>
|
||||||
|
<th>Voucher</th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@for (t of depreciationTargets; track t.holding.certNo) {
|
@for (r of valuationRuns; track r.id) {
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>{{ r.valuationDate }}</td>
|
||||||
|
<td>{{ r.category }}</td>
|
||||||
|
<td class="num">₹{{ r.classes[0].required | number: '1.2-2' }}</td>
|
||||||
|
<td class="num">₹{{ r.classes[1].required | number: '1.2-2' }}</td>
|
||||||
|
<td class="num">₹{{ r.classes[2].required | number: '1.2-2' }}</td>
|
||||||
|
<td class="num">₹{{ r.requiredTotal | number: '1.2-2' }}</td>
|
||||||
|
<td class="num">₹{{ r.delta | number: '1.2-2' }}</td>
|
||||||
|
<td>{{ r.voucherNo || '—' }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="pg-chip">{{ t.holding.certNo }}</span>
|
<button type="button" class="pg-btn pg-btn--ghost" (click)="openValuationRun(r.id)">Lines</button>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ t.holding.scriptName }}</td>
|
|
||||||
<td class="num">{{ t.holding.purchaseRate }}</td>
|
|
||||||
<td class="num">{{ t.marketPrice }}</td>
|
|
||||||
<td class="num col-out">₹{{ t.depreciation | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
} @empty {
|
} @empty {
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="pg-empty">No holdings require depreciation provisioning.</td>
|
<td colspan="9" class="pg-empty">No valuation run yet.</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@if (valuationOpened; as o) {
|
||||||
|
<div class="pos-section-label">{{ o.category }} valuation of {{ o.valuationDate }}</div>
|
||||||
|
<table class="pg-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Deal No</th>
|
||||||
|
<th>Script</th>
|
||||||
|
<th class="num">Units</th>
|
||||||
|
<th class="num">Book Value</th>
|
||||||
|
<th class="num">Rate / NAV</th>
|
||||||
|
<th>Rate Date</th>
|
||||||
|
<th class="num">Market Value</th>
|
||||||
|
<th class="num">Gain / Loss</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (l of o.lines; track l.purchaseId) {
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="pg-chip">{{ l.dealNo }}</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ l.scriptCode }} {{ l.scriptDesc }}</td>
|
||||||
|
<td class="num">{{ l.units | number: '1.0-6' }}</td>
|
||||||
|
<td class="num">₹{{ l.bookValue | number: '1.2-2' }}</td>
|
||||||
|
<td class="num">{{ l.rate | number: '1.2-4' }}</td>
|
||||||
|
<td>{{ l.rateDate }}</td>
|
||||||
|
<td class="num">₹{{ l.marketValue | number: '1.2-2' }}</td>
|
||||||
|
<td class="num" [class.col-out]="l.difference < 0">₹{{ l.difference | number: '1.2-2' }}</td>
|
||||||
|
</tr>
|
||||||
|
} @empty {
|
||||||
|
<tr>
|
||||||
|
<td colspan="8" class="pg-empty">No holding was valued (the run only wrote the reserve back).</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1615,40 +1759,87 @@
|
|||||||
<div class="pos-section-label">Investment - Mutual Fund Transfer (Switch between schemes)</div>
|
<div class="pos-section-label">Investment - Mutual Fund Transfer (Switch between schemes)</div>
|
||||||
<div class="inv-form-grid">
|
<div class="inv-form-grid">
|
||||||
<label class="inv-form-field">
|
<label class="inv-form-field">
|
||||||
<span>From Holding *</span>
|
<span>* From Fund (Deal No)</span>
|
||||||
<select [(ngModel)]="mfFromCertNo">
|
<select [(ngModel)]="mfSwitchPurchaseId" (ngModelChange)="onMfSwitchDealChange()">
|
||||||
<option value="">-- Select --</option>
|
<option [ngValue]="null">-- Select --</option>
|
||||||
@for (h of mfHoldings; track h.certNo) {
|
@for (h of mfHoldings; track h.id) {
|
||||||
<option [value]="h.certNo">{{ h.certNo }} — {{ h.scriptName }}</option>
|
<option [ngValue]="h.id">{{ h.dealNo }} — {{ h.scriptCode }} {{ h.scriptName }}</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
@if (mfFromHolding; as h) {
|
@if (mfFromHolding; as h) {
|
||||||
<label class="inv-form-field"><span>Units Held</span><input type="text" [value]="h.units" readonly /></label>
|
<label class="inv-form-field"
|
||||||
<label class="inv-form-field"><span>NAV</span><input type="text" [value]="h.purchaseRate" readonly /></label>
|
><span>Units Held</span><input type="text" [value]="h.units | number: '1.0-6'" readonly
|
||||||
|
/></label>
|
||||||
|
<label class="inv-form-field"
|
||||||
|
><span>Book Value Held</span
|
||||||
|
><input type="text" [value]="'₹' + (carryingValue(h) | number: '1.2-2')" readonly
|
||||||
|
/></label>
|
||||||
}
|
}
|
||||||
|
<label class="inv-form-field"
|
||||||
|
><span>* Switch Date</span
|
||||||
|
><mifosx-date-ddmmyyyy [(ngModel)]="mfSwitchDate" (ngModelChange)="loadMfSwitchRates()"></mifosx-date-ddmmyyyy
|
||||||
|
></label>
|
||||||
|
<label class="inv-form-field"
|
||||||
|
><span>* Units to Switch</span><input type="number" [(ngModel)]="mfSwitchUnits" min="0" step="0.001"
|
||||||
|
/></label>
|
||||||
<label class="inv-form-field">
|
<label class="inv-form-field">
|
||||||
<span>To Product *</span>
|
<span>* NAV of this fund</span>
|
||||||
<select [(ngModel)]="mfToProduct">
|
<input type="number" [(ngModel)]="mfSwitchFromNav" min="0" step="0.0001" />
|
||||||
|
@if (mfSwitchFromRate; as r) {
|
||||||
|
<small>Daily Script Master {{ r.rateDate }}: {{ r.closeRate | number: '1.2-4' }}</small>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
<label class="inv-form-field">
|
||||||
|
<span>* Switch into Scheme</span>
|
||||||
|
<select [(ngModel)]="mfSwitchToScriptCode" (ngModelChange)="onMfSwitchTargetChange()">
|
||||||
<option value="">-- Select --</option>
|
<option value="">-- Select --</option>
|
||||||
@for (p of mfProductOptions; track p.productCode) {
|
@for (x of mfSwitchTargets; track x.scriptCode) {
|
||||||
<option [value]="p.productName">{{ p.productName }} ({{ p.amcName }})</option>
|
<option [value]="x.scriptCode">{{ x.scriptCode }} — {{ x.scriptName }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="inv-form-field">
|
||||||
|
<span>* NAV of the new scheme</span>
|
||||||
|
<input type="number" [(ngModel)]="mfSwitchToNav" min="0" step="0.0001" />
|
||||||
|
@if (mfSwitchToRate; as r) {
|
||||||
|
<small>Daily Script Master {{ r.rateDate }}: {{ r.closeRate | number: '1.2-4' }}</small>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
<label class="inv-form-field">
|
||||||
|
<span>* Sub Type of the new scheme</span>
|
||||||
|
<select [(ngModel)]="mfSwitchSubType">
|
||||||
|
@for (s of mfSwitchSubTypes; track s.subTypeCode) {
|
||||||
|
<option [value]="s.subTypeName">{{ s.subTypeName }}</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="inv-form-field"
|
<label class="inv-form-field"
|
||||||
><span>Units to Transfer *</span><input type="number" [(ngModel)]="mfTransferUnits" min="0" step="0.001"
|
><span>Remarks</span><input type="text" maxlength="400" [(ngModel)]="mfSwitchRemarks"
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Transfer Date *</span><mifosx-date-ddmmyyyy [(ngModel)]="mfTransferDate"></mifosx-date-ddmmyyyy
|
|
||||||
></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Transfer Value (computed)</span
|
|
||||||
><input type="text" [value]="'₹' + (mfTransferValue | number: '1.2-2')" readonly
|
|
||||||
/></label>
|
/></label>
|
||||||
</div>
|
</div>
|
||||||
|
@if (mfFromHolding && mfSwitchToScript && mfSwitchValue) {
|
||||||
|
<div class="pos-section-label">Accounting (dated {{ mfSwitchDate }}, no bank entry)</div>
|
||||||
|
<p class="pg-sub" style="padding: 0 16px">
|
||||||
|
Switch value ₹{{ mfSwitchValue | number: '1.2-2' }} · cost of the units ₹{{
|
||||||
|
mfSwitchCost | number: '1.2-2'
|
||||||
|
}}
|
||||||
|
· new units {{ mfSwitchNewUnits | number: '1.0-3' }} of {{ mfSwitchToScript.scriptCode }}
|
||||||
|
</p>
|
||||||
|
<p class="pg-sub" style="padding: 0 16px">
|
||||||
|
Dr {{ mfSwitchToScript.glCode }} Investment (new scheme) ₹{{ mfSwitchValue | number: '1.2-2' }} · Cr
|
||||||
|
{{ mfFromHolding.glCode }} Investment (this fund) ₹{{ mfSwitchCost | number: '1.2-2' }}
|
||||||
|
@if (mfSwitchGain > 0) {
|
||||||
|
· Cr 400402 Interest on Bonds ₹{{ mfSwitchGain | number: '1.2-2' }}
|
||||||
|
} @else if (mfSwitchGain < 0) {
|
||||||
|
· Dr 500605 Loss on Investments ₹{{ -mfSwitchGain | number: '1.2-2' }}
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
<div class="inv-form-actions">
|
<div class="inv-form-actions">
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="saveMfTransfer()">Save</button>
|
<button type="button" class="pg-btn pg-btn--primary" [disabled]="mfSwitchSaving" (click)="saveMfSwitch()">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="setTab('holdings')">Exit</button>
|
<button type="button" class="pg-btn pg-btn--ghost" (click)="setTab('holdings')">Exit</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
SUBTYPE_MASTER,
|
SUBTYPE_MASTER,
|
||||||
BROKER_MASTER,
|
BROKER_MASTER,
|
||||||
SGL_BANK_MASTER,
|
SGL_BANK_MASTER,
|
||||||
MUTUAL_FUND_PRODUCTS,
|
|
||||||
LIEN_REASON_MASTER,
|
LIEN_REASON_MASTER,
|
||||||
CATEGORY_OPTIONS,
|
CATEGORY_OPTIONS,
|
||||||
DEAL_TYPE_OPTIONS,
|
DEAL_TYPE_OPTIONS,
|
||||||
@@ -73,6 +72,7 @@ import {
|
|||||||
InvestmentSecurityTransferDto,
|
InvestmentSecurityTransferDto,
|
||||||
InvestmentNonSlrLimitDto,
|
InvestmentNonSlrLimitDto,
|
||||||
InvestmentService,
|
InvestmentService,
|
||||||
|
InvestmentValuationDto,
|
||||||
NON_SLR_TYPES,
|
NON_SLR_TYPES,
|
||||||
apiErrorMessage,
|
apiErrorMessage,
|
||||||
toHolding,
|
toHolding,
|
||||||
@@ -175,7 +175,7 @@ interface AccountingPreviewLine {
|
|||||||
* Combined Govt Security / Non-SLR / Bond / Mutual Fund treasury screen, mirroring the legacy
|
* Combined Govt Security / Non-SLR / Bond / Mutual Fund treasury screen, mirroring the legacy
|
||||||
* "03 Govt./Bond Entry" submenu in full: Purchase, Redemption, Interest Calculation, Sell,
|
* "03 Govt./Bond Entry" submenu in full: Purchase, Redemption, Interest Calculation, Sell,
|
||||||
* Write-off, Amortize, Amortize Yearly Process, GOI Category Transfer, Investment Depreciation
|
* Write-off, Amortize, Amortize Yearly Process, GOI Category Transfer, Investment Depreciation
|
||||||
* and Mutual Fund Transfer. Dummy in-memory data; Save appends to the local arrays.
|
* (market valuation) and Mutual Fund Transfer (switch), all on the backend.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mifosx-bond-investment',
|
selector: 'mifosx-bond-investment',
|
||||||
@@ -278,7 +278,8 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
'REDEEM',
|
'REDEEM',
|
||||||
'TRANSFER',
|
'TRANSFER',
|
||||||
'AMORTIZE',
|
'AMORTIZE',
|
||||||
'WRITE_OFF'
|
'WRITE_OFF',
|
||||||
|
'SWITCH'
|
||||||
].includes(t.txnType)
|
].includes(t.txnType)
|
||||||
);
|
);
|
||||||
this.txns = [
|
this.txns = [
|
||||||
@@ -311,7 +312,6 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
readonly brokerOptions = BROKER_MASTER;
|
readonly brokerOptions = BROKER_MASTER;
|
||||||
readonly sglBankOptions = SGL_BANK_MASTER;
|
readonly sglBankOptions = SGL_BANK_MASTER;
|
||||||
readonly mfProductOptions = MUTUAL_FUND_PRODUCTS;
|
|
||||||
readonly lienReasonOptions = LIEN_REASON_MASTER;
|
readonly lienReasonOptions = LIEN_REASON_MASTER;
|
||||||
readonly categoryOptions = CATEGORY_OPTIONS;
|
readonly categoryOptions = CATEGORY_OPTIONS;
|
||||||
readonly dealTypeOptions = DEAL_TYPE_OPTIONS;
|
readonly dealTypeOptions = DEAL_TYPE_OPTIONS;
|
||||||
@@ -354,15 +354,9 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
|
|
||||||
// GOI Category Transfer
|
// GOI Category Transfer
|
||||||
|
|
||||||
// Investment Depreciation
|
// Investment Depreciation (market valuation of AFS / HFT)
|
||||||
depreciationDate = this.asOnDate;
|
depreciationDate = this.asOnDate;
|
||||||
depreciationCategory = '';
|
depreciationCategory: 'AFS' | 'HFT' = 'AFS';
|
||||||
|
|
||||||
// Mutual Fund Transfer
|
|
||||||
mfFromCertNo = '';
|
|
||||||
mfToProduct = '';
|
|
||||||
mfTransferUnits: number | null = null;
|
|
||||||
mfTransferDate = this.asOnDate;
|
|
||||||
|
|
||||||
readonly bondTypeOptions: Array<{ value: BondType; label: string }> = [
|
readonly bondTypeOptions: Array<{ value: BondType; label: string }> = [
|
||||||
{ value: 'GOVT_SECURITY', label: 'GOVT SECURITY' },
|
{ value: 'GOVT_SECURITY', label: 'GOVT SECURITY' },
|
||||||
@@ -373,6 +367,9 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
|
|
||||||
setTab(tab: BondTab): void {
|
setTab(tab: BondTab): void {
|
||||||
this.activeTab = tab;
|
this.activeTab = tab;
|
||||||
|
if (tab === 'depreciation') {
|
||||||
|
this.loadValuationRuns();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setBondType(type: BondType): void {
|
setBondType(type: BondType): void {
|
||||||
@@ -1737,123 +1734,245 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------- Investment Depreciation --------------------
|
// -------------------- Investment Depreciation (market valuation, MTM) --------------------
|
||||||
/** AFS/HFT holdings whose market price is below book value must be marked down (RBI norms). */
|
/**
|
||||||
get depreciationTargets(): Array<{ holding: InvestmentHolding; marketPrice: number; depreciation: number }> {
|
* RBI: AFS holdings are valued at market quarterly and HFT monthly (or more often); HTM is carried at cost. The backend
|
||||||
return this.holdings
|
* values each security at the Daily Script Master rate, nets gains and losses within each class, and posts only the
|
||||||
.filter((h) => h.status === 'ACTIVE' && h.instrumentType !== 'MUTUAL_FUND')
|
* change in the Investment Depreciation Reserve. The book value of the holdings does not change.
|
||||||
.map((h) => {
|
*/
|
||||||
const md = this.scriptOptions.find((s) => s.scriptCode === h.scriptCode);
|
valuationPreview: InvestmentValuationDto | null = null;
|
||||||
const marketPrice = md ? h.purchaseRate * 0.995 : h.purchaseRate;
|
valuationError = '';
|
||||||
const depreciation =
|
valuationLoading = false;
|
||||||
marketPrice < h.purchaseRate ? Math.round((h.faceValue * (h.purchaseRate - marketPrice)) / 100) : 0;
|
valuationPosting = false;
|
||||||
return { holding: h, marketPrice: +marketPrice.toFixed(2), depreciation };
|
valuationRuns: InvestmentValuationDto[] = [];
|
||||||
})
|
/** A past run opened from the history, with its lines. */
|
||||||
.filter((r) => r.depreciation > 0);
|
valuationOpened: InvestmentValuationDto | null = null;
|
||||||
|
|
||||||
|
readonly valuationClassLabels: Record<string, string> = {
|
||||||
|
GOVT_SECURITY: 'Govt Securities',
|
||||||
|
CORPORATE_BOND: 'Corporate Bonds',
|
||||||
|
MUTUAL_FUND: 'Mutual Funds'
|
||||||
|
};
|
||||||
|
|
||||||
|
loadValuationRuns(): void {
|
||||||
|
this.investmentService.getValuations().subscribe({
|
||||||
|
next: (runs) => (this.valuationRuns = runs),
|
||||||
|
error: (err) => this.toast(`Could not load valuation runs: ${apiErrorMessage(err)}`)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get depreciationTotal(): number {
|
previewValuation(): void {
|
||||||
return this.depreciationTargets.reduce((s, r) => s + r.depreciation, 0);
|
this.valuationPreview = null;
|
||||||
}
|
this.valuationError = '';
|
||||||
|
if (!this.depreciationDate || !this.depreciationCategory) {
|
||||||
runDepreciation(): void {
|
this.valuationError = 'Enter the valuation date and choose AFS or HFT.';
|
||||||
const targets = this.depreciationTargets;
|
|
||||||
if (targets.length === 0) {
|
|
||||||
this.toast('No holdings require depreciation provisioning.');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newTxns: InvestmentTxn[] = targets.map((t, i) => ({
|
this.valuationLoading = true;
|
||||||
certNo: t.holding.certNo,
|
this.investmentService.previewValuation(this.depreciationDate, this.depreciationCategory).subscribe({
|
||||||
instrumentType: t.holding.instrumentType,
|
next: (v) => {
|
||||||
txnType: 'AMORTIZE',
|
this.valuationLoading = false;
|
||||||
voucherNo: `INV/DEPR/${this.depreciationDate.replace(/-/g, '')}-${i + 1}`,
|
this.valuationPreview = v;
|
||||||
txnDate: this.depreciationDate,
|
},
|
||||||
amount: t.depreciation,
|
error: (err) => {
|
||||||
particulars: `Investment depreciation provision - ${t.holding.scriptName} (market ${t.marketPrice})`
|
this.valuationLoading = false;
|
||||||
}));
|
this.valuationError = apiErrorMessage(err);
|
||||||
this.txns = [
|
}
|
||||||
...newTxns,
|
});
|
||||||
...this.txns
|
|
||||||
];
|
|
||||||
this.toast(
|
|
||||||
`Depreciation provisioned for ${targets.length} holding(s). Total ₹${this.depreciationTotal.toLocaleString('en-IN')}.`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------- Mutual Fund Transfer --------------------
|
/** Posts the change in IDR worked out by the preview; the backend values the holdings again with the same rules. */
|
||||||
|
postValuation(): void {
|
||||||
|
const v = this.valuationPreview;
|
||||||
|
if (!v?.postable || this.valuationPosting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.valuationPosting = true;
|
||||||
|
this.investmentService
|
||||||
|
.runValuation({ valuationDate: v.valuationDate, category: v.category, dateFormat: 'dd-MM-yyyy', locale: 'en' })
|
||||||
|
.subscribe({
|
||||||
|
next: (res) => {
|
||||||
|
this.valuationPosting = false;
|
||||||
|
this.toast(
|
||||||
|
v.delta === 0
|
||||||
|
? `Valuation of ${v.valuationDate} recorded; the IDR needed is unchanged, so no voucher.`
|
||||||
|
: `Valuation of ${v.valuationDate} posted — ${res.resourceIdentifier ?? ''}.`
|
||||||
|
);
|
||||||
|
this.valuationPreview = null;
|
||||||
|
this.loadValuationRuns();
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.valuationPosting = false;
|
||||||
|
this.toast(apiErrorMessage(err));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openValuationRun(id: number | undefined): void {
|
||||||
|
if (id == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.investmentService.getValuation(id).subscribe({
|
||||||
|
next: (v) => (this.valuationOpened = v),
|
||||||
|
error: (err) => this.toast(apiErrorMessage(err))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- Mutual Fund Transfer (switch) --------------------
|
||||||
|
/**
|
||||||
|
* Units of one scheme are redeemed at its NAV and put into another scheme the same day; no money comes to the bank.
|
||||||
|
* Voucher: Dr new scheme's investment GL (value) / Cr old scheme's GL (cost of the units); gain 400402, loss 500605.
|
||||||
|
*/
|
||||||
|
mfSwitchPurchaseId: number | null = null;
|
||||||
|
mfSwitchUnits: number | null = null;
|
||||||
|
mfSwitchFromNav: number | null = null;
|
||||||
|
mfSwitchToScriptCode = '';
|
||||||
|
mfSwitchToNav: number | null = null;
|
||||||
|
mfSwitchSubType = 'DEBT - LIQUID';
|
||||||
|
mfSwitchDate = this.asOnDate;
|
||||||
|
mfSwitchRemarks = '';
|
||||||
|
mfSwitchSaving = false;
|
||||||
|
/** Latest Daily Script Master NAVs on the switch date, as hints. */
|
||||||
|
mfSwitchFromRate: InvestmentDailyRateDto | null = null;
|
||||||
|
mfSwitchToRate: InvestmentDailyRateDto | null = null;
|
||||||
|
|
||||||
get mfHoldings(): InvestmentHolding[] {
|
get mfHoldings(): InvestmentHolding[] {
|
||||||
return this.holdings.filter((h) => h.instrumentType === 'MUTUAL_FUND' && h.status === 'ACTIVE');
|
return this.holdings.filter((h) => h.instrumentType === 'MUTUAL_FUND' && h.status === 'ACTIVE' && !!h.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
get mfFromHolding(): InvestmentHolding | undefined {
|
get mfFromHolding(): InvestmentHolding | undefined {
|
||||||
return this.holdings.find((h) => h.certNo === this.mfFromCertNo);
|
return this.holdings.find((h) => h.id === this.mfSwitchPurchaseId);
|
||||||
}
|
}
|
||||||
|
|
||||||
get mfTransferValue(): number {
|
/** Other active mutual fund schemes saved in Script Master. */
|
||||||
|
get mfSwitchTargets(): ScriptMaster[] {
|
||||||
|
const from = this.mfFromHolding;
|
||||||
|
return this.scriptOptions.filter(
|
||||||
|
(x) => !!x.id && x.active !== false && x.instrumentType === 'MUTUAL_FUND' && x.scriptCode !== from?.scriptCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get mfSwitchToScript(): ScriptMaster | undefined {
|
||||||
|
return this.scriptOptions.find((x) => x.scriptCode === this.mfSwitchToScriptCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
get mfSwitchSubTypes(): typeof SUBTYPE_MASTER {
|
||||||
|
return SUBTYPE_MASTER.filter((s) => s.instrumentType === 'MUTUAL_FUND');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Units × NAV of the scheme left. */
|
||||||
|
get mfSwitchValue(): number {
|
||||||
|
return this.mfSwitchUnits && this.mfSwitchFromNav
|
||||||
|
? Math.round(this.mfSwitchUnits * this.mfSwitchFromNav * 100) / 100
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The units' share of the book value still held, as the backend works it out for a sale. */
|
||||||
|
get mfSwitchCost(): number {
|
||||||
const h = this.mfFromHolding;
|
const h = this.mfFromHolding;
|
||||||
if (!h || !this.mfTransferUnits) {
|
if (!h || !this.mfSwitchUnits || !h.units) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return Math.round(this.mfTransferUnits * h.purchaseRate);
|
const book = this.carryingValue(h);
|
||||||
|
return this.mfSwitchUnits >= h.units ? book : Math.round(((book * this.mfSwitchUnits) / h.units) * 100) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveMfTransfer(): void {
|
get mfSwitchGain(): number {
|
||||||
|
return Math.round((this.mfSwitchValue - this.mfSwitchCost) * 100) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
get mfSwitchNewUnits(): number {
|
||||||
|
return this.mfSwitchToNav ? Math.round((this.mfSwitchValue / this.mfSwitchToNav) * 1000) / 1000 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMfSwitchDealChange(): void {
|
||||||
|
this.mfSwitchUnits = this.mfFromHolding?.units ?? null;
|
||||||
|
this.mfSwitchFromNav = null;
|
||||||
|
this.loadMfSwitchRates();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fills the NAVs from Daily Script Master for the switch date when one has been entered. */
|
||||||
|
loadMfSwitchRates(): void {
|
||||||
|
const fromId = this.scriptOptions.find((x) => x.scriptCode === this.mfFromHolding?.scriptCode)?.id;
|
||||||
|
const toId = this.mfSwitchToScript?.id;
|
||||||
|
this.mfSwitchFromRate = null;
|
||||||
|
this.mfSwitchToRate = null;
|
||||||
|
if (fromId && this.mfSwitchDate) {
|
||||||
|
this.investmentService.getLatestDailyRate(fromId, this.mfSwitchDate).subscribe({
|
||||||
|
next: (r) => {
|
||||||
|
this.mfSwitchFromRate = r;
|
||||||
|
this.mfSwitchFromNav = this.mfSwitchFromNav ?? r?.closeRate ?? null;
|
||||||
|
},
|
||||||
|
error: () => (this.mfSwitchFromRate = null)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (toId && this.mfSwitchDate) {
|
||||||
|
this.investmentService.getLatestDailyRate(toId, this.mfSwitchDate).subscribe({
|
||||||
|
next: (r) => {
|
||||||
|
this.mfSwitchToRate = r;
|
||||||
|
this.mfSwitchToNav = this.mfSwitchToNav ?? r?.closeRate ?? null;
|
||||||
|
},
|
||||||
|
error: () => (this.mfSwitchToRate = null)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMfSwitchTargetChange(): void {
|
||||||
|
this.mfSwitchToNav = null;
|
||||||
|
this.loadMfSwitchRates();
|
||||||
|
}
|
||||||
|
|
||||||
|
saveMfSwitch(): void {
|
||||||
const h = this.mfFromHolding;
|
const h = this.mfFromHolding;
|
||||||
if (!h || !this.mfToProduct || !this.mfTransferUnits) {
|
const to = this.mfSwitchToScript;
|
||||||
this.toast('Select source holding, target product and units to transfer.');
|
if (
|
||||||
|
!h?.id ||
|
||||||
|
!to?.id ||
|
||||||
|
!this.mfSwitchUnits ||
|
||||||
|
!this.mfSwitchFromNav ||
|
||||||
|
!this.mfSwitchToNav ||
|
||||||
|
!this.mfSwitchDate
|
||||||
|
) {
|
||||||
|
this.toast('Select the fund, the units, both NAVs, the scheme to switch into and the date.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.mfTransferUnits > h.units) {
|
if (this.mfSwitchSaving) {
|
||||||
this.toast(`Units cannot exceed units held (${h.units}).`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const value = this.mfTransferValue;
|
this.mfSwitchSaving = true;
|
||||||
h.units = +(h.units - this.mfTransferUnits).toFixed(3);
|
this.investmentService
|
||||||
h.faceValue = Math.round(h.units * h.purchaseRate);
|
.switchFund(h.id, {
|
||||||
if (h.units <= 0) {
|
switchDate: this.mfSwitchDate,
|
||||||
h.status = 'SOLD';
|
units: this.mfSwitchUnits,
|
||||||
}
|
fromNav: this.mfSwitchFromNav,
|
||||||
const product = this.mfProductOptions.find((p) => p.productName === this.mfToProduct);
|
toScriptId: to.id,
|
||||||
const newCert = `MF-${5000 + this.holdings.filter((x) => x.instrumentType === 'MUTUAL_FUND').length + 1}`;
|
toNav: this.mfSwitchToNav,
|
||||||
this.holdings = [
|
subType: this.mfSwitchSubType,
|
||||||
{
|
...(this.mfSwitchRemarks ? { remarks: this.mfSwitchRemarks } : {}),
|
||||||
certNo: newCert,
|
dateFormat: 'dd-MM-yyyy',
|
||||||
instrumentType: 'MUTUAL_FUND',
|
locale: 'en'
|
||||||
scriptCode: product?.productCode || this.mfToProduct,
|
})
|
||||||
scriptName: this.mfToProduct,
|
.subscribe({
|
||||||
party: product?.amcName || '',
|
next: (res) => {
|
||||||
subType: product?.category || 'DEBT',
|
this.mfSwitchSaving = false;
|
||||||
faceValue: value,
|
this.toast(
|
||||||
purchaseRate: h.purchaseRate,
|
`Switched ${this.mfSwitchUnits} units of deal ${h.dealNo} into ${to.scriptCode} — new deal ${res.resourceIdentifier ?? ''}.`
|
||||||
units: this.mfTransferUnits,
|
);
|
||||||
purchaseDate: this.mfTransferDate,
|
this.mfSwitchPurchaseId = null;
|
||||||
maturityDate: addPeriod(this.mfTransferDate, 1, 0),
|
this.mfSwitchUnits = null;
|
||||||
interestRate: h.interestRate,
|
this.mfSwitchFromNav = null;
|
||||||
interestFreq: 'CUMULATIVE',
|
this.mfSwitchToScriptCode = '';
|
||||||
glCode: '310050',
|
this.mfSwitchToNav = null;
|
||||||
glName: 'INVESTMENT IN MUTUAL FUNDS',
|
this.mfSwitchRemarks = '';
|
||||||
crToAc: h.crToAc,
|
this.mfSwitchFromRate = null;
|
||||||
brokerCode: '',
|
this.mfSwitchToRate = null;
|
||||||
status: 'ACTIVE'
|
this.loadPurchases();
|
||||||
},
|
},
|
||||||
...this.holdings
|
error: (err) => {
|
||||||
];
|
this.mfSwitchSaving = false;
|
||||||
this.txns = [
|
this.toast(apiErrorMessage(err));
|
||||||
{
|
}
|
||||||
certNo: newCert,
|
});
|
||||||
instrumentType: 'MUTUAL_FUND',
|
|
||||||
txnType: 'PURCHASE',
|
|
||||||
voucherNo: `INV/MF-TRF/${newCert}`,
|
|
||||||
txnDate: this.mfTransferDate,
|
|
||||||
amount: value,
|
|
||||||
particulars: `Mutual Fund switch from ${h.scriptName} to ${this.mfToProduct} (${this.mfTransferUnits} units)`
|
|
||||||
},
|
|
||||||
...this.txns
|
|
||||||
];
|
|
||||||
this.toast(`Transferred ${this.mfTransferUnits} units to ${this.mfToProduct} as ${newCert}.`);
|
|
||||||
this.mfFromCertNo = '';
|
|
||||||
this.mfToProduct = '';
|
|
||||||
this.mfTransferUnits = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------- Helpers --------------------
|
// -------------------- Helpers --------------------
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export type TxnType =
|
|||||||
| 'WRITE_OFF'
|
| 'WRITE_OFF'
|
||||||
| 'AMORTIZE'
|
| 'AMORTIZE'
|
||||||
| 'TRANSFER'
|
| 'TRANSFER'
|
||||||
|
| 'SWITCH'
|
||||||
| 'PART_PAYMENT'
|
| 'PART_PAYMENT'
|
||||||
| 'PREMATURE'
|
| 'PREMATURE'
|
||||||
| 'RENEWAL';
|
| 'RENEWAL';
|
||||||
|
|||||||
@@ -481,6 +481,36 @@ export class InvestmentService {
|
|||||||
return this.http.post<{ resourceIdentifier?: string }>(`/investment/purchases/${purchaseId}/redeem`, body);
|
return this.http.post<{ resourceIdentifier?: string }>(`/investment/purchases/${purchaseId}/redeem`, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Mutual fund switch: units leave this fund at its NAV and a new deal opens in the other scheme; the new deal no comes back. */
|
||||||
|
switchFund(purchaseId: number, body: object): Observable<{ resourceId?: number; resourceIdentifier?: string }> {
|
||||||
|
return this.http.post<{ resourceId?: number; resourceIdentifier?: string }>(
|
||||||
|
`/investment/purchases/${purchaseId}/switch`,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- Market valuation (MTM) --------------------
|
||||||
|
/** What a valuation of AFS / HFT holdings on a date would find and post, without saving. */
|
||||||
|
previewValuation(valuationDate: string, category: string): Observable<InvestmentValuationDto> {
|
||||||
|
return this.http.get<InvestmentValuationDto>('/investment/valuations/preview', {
|
||||||
|
params: { valuationDate, category }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Runs the valuation and posts the change in the Investment Depreciation Reserve. */
|
||||||
|
runValuation(body: object): Observable<{ resourceId?: number; resourceIdentifier?: string }> {
|
||||||
|
return this.http.post<{ resourceId?: number; resourceIdentifier?: string }>('/investment/valuations', body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Valuation runs, newest first (without their lines). */
|
||||||
|
getValuations(): Observable<InvestmentValuationDto[]> {
|
||||||
|
return this.http.get<InvestmentValuationDto[]>('/investment/valuations');
|
||||||
|
}
|
||||||
|
|
||||||
|
getValuation(id: number): Observable<InvestmentValuationDto> {
|
||||||
|
return this.http.get<InvestmentValuationDto>(`/investment/valuations/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
/** Interest lines already posted (coupons and provisions), newest first. */
|
/** Interest lines already posted (coupons and provisions), newest first. */
|
||||||
getInterestPostings(): Observable<InvestmentInterestPostingDto[]> {
|
getInterestPostings(): Observable<InvestmentInterestPostingDto[]> {
|
||||||
return this.http.get<InvestmentInterestPostingDto[]>('/investment/interest-postings');
|
return this.http.get<InvestmentInterestPostingDto[]>('/investment/interest-postings');
|
||||||
@@ -826,6 +856,20 @@ export function toSaleTxn(x: InvestmentSaleDto): InvestmentTxn {
|
|||||||
glName: `Loss on Investments / Cr ${x.investmentGlCode} (txn ${x.glTransactionId})`
|
glName: `Loss on Investments / Cr ${x.investmentGlCode} (txn ${x.glTransactionId})`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (x.saleType === 'SWITCH') {
|
||||||
|
const result = x.profitLoss >= 0 ? `profit ₹${x.profitLoss}` : `loss ₹${-x.profitLoss}`;
|
||||||
|
return {
|
||||||
|
certNo: x.certNo,
|
||||||
|
instrumentType: x.bondType as InstrumentType,
|
||||||
|
txnType: 'SWITCH',
|
||||||
|
voucherNo: x.voucherNo ?? `SWT/${x.dealNo}/${x.id}`,
|
||||||
|
txnDate: x.settlementDate,
|
||||||
|
amount: x.consideration,
|
||||||
|
particulars: `Switched ${x.units} units @ NAV ${x.saleRate} - Deal ${x.dealNo} - ${x.scriptDesc} (${result}) - ${x.remarks ?? ''}`,
|
||||||
|
glCode: x.investmentGlCode,
|
||||||
|
glName: `Switched to another scheme, no bank entry (txn ${x.glTransactionId})`
|
||||||
|
};
|
||||||
|
}
|
||||||
const redeem = x.saleType === 'REDEEM';
|
const redeem = x.saleType === 'REDEEM';
|
||||||
// A Govt Security redeemed below book writes off its premium; a non-SLR shortfall is a loss (500605).
|
// A Govt Security redeemed below book writes off its premium; a non-SLR shortfall is a loss (500605).
|
||||||
const pl =
|
const pl =
|
||||||
@@ -1040,7 +1084,7 @@ export interface InvestmentSaleDto {
|
|||||||
bondType: string;
|
bondType: string;
|
||||||
scriptCode: string;
|
scriptCode: string;
|
||||||
scriptDesc: string;
|
scriptDesc: string;
|
||||||
saleType: 'SELL' | 'REDEEM' | 'WRITEOFF' | 'AMORTISE';
|
saleType: 'SELL' | 'REDEEM' | 'WRITEOFF' | 'AMORTISE' | 'SWITCH';
|
||||||
partyId?: number;
|
partyId?: number;
|
||||||
partyName?: string;
|
partyName?: string;
|
||||||
dealDate: string;
|
dealDate: string;
|
||||||
@@ -1075,6 +1119,47 @@ export interface InvestmentSaleDto {
|
|||||||
settledOnDate?: string;
|
settledOnDate?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A market valuation (MTM) of AFS or HFT holdings: a preview (no id) or a posted run. */
|
||||||
|
export interface InvestmentValuationDto {
|
||||||
|
id?: number;
|
||||||
|
valuationDate: string;
|
||||||
|
category: string;
|
||||||
|
requiredTotal: number;
|
||||||
|
/** IDR held from the category's previous run. */
|
||||||
|
previousTotal: number;
|
||||||
|
/** More provision when positive, a write-back when negative. */
|
||||||
|
delta: number;
|
||||||
|
provisionGlCode: string;
|
||||||
|
reserveGlCode: string;
|
||||||
|
glTransactionId?: string;
|
||||||
|
voucherNo?: string;
|
||||||
|
lastRunDate?: string;
|
||||||
|
missingRates: string[];
|
||||||
|
postable: boolean;
|
||||||
|
classes: Array<{
|
||||||
|
assetClass: 'GOVT_SECURITY' | 'CORPORATE_BOND' | 'MUTUAL_FUND';
|
||||||
|
bookValue: number;
|
||||||
|
marketValue: number;
|
||||||
|
netDifference: number;
|
||||||
|
required: number;
|
||||||
|
}>;
|
||||||
|
lines: Array<{
|
||||||
|
purchaseId: number;
|
||||||
|
dealNo: string;
|
||||||
|
certNo: string;
|
||||||
|
bondType: string;
|
||||||
|
scriptCode: string;
|
||||||
|
scriptDesc: string;
|
||||||
|
assetClass: string;
|
||||||
|
units: number;
|
||||||
|
bookValue: number;
|
||||||
|
rate: number;
|
||||||
|
rateDate: string;
|
||||||
|
marketValue: number;
|
||||||
|
difference: number;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
/** A GOI Category Transfer: a holding moved to another security of the same terms. */
|
/** A GOI Category Transfer: a holding moved to another security of the same terms. */
|
||||||
export interface InvestmentSecurityTransferDto {
|
export interface InvestmentSecurityTransferDto {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user