feat(investment,reconciliation): add Export to Excel across Investment, fix export popup

Adds a shared Export to Excel utility (investment-export.ts) and wires it
into every listing/report screen in the Investment module — Dashboard
(break-up + allocation), Reports (all ~20 report shapes), Interest (per
active tab), the FD/Bond/Share Holdings registers, and Masters (per active
master tab). Entry/transaction forms intentionally have no export button.
No sign-off is included here, matching the request to keep that feature
reconciliation-only.

Also fixes the Reconciliation export flow: the Prepared/Checked/Approved
sign-off is now an optional one-off popup shown when "Export to Excel" is
clicked (fields are blank by default and downloading works whether or not
they're filled), replacing the earlier always-visible sign-off card. Also
fixes three reconciliation reports (Sundry Dr/Cr, NEFT/RTGS, UDIR) whose
labels contain "/" — an Excel-forbidden worksheet-name character — which
was silently aborting their export.
This commit is contained in:
2026-08-01 19:20:53 +05:30
parent 8ac4010967
commit 0832017508
18 changed files with 1610 additions and 0 deletions
@@ -32,6 +32,7 @@ import {
LienReason,
InstrumentType
} from '../investment-data';
import { exportWorkbook, ExportSheetSpec } from '../investment-export';
type MasterTab =
| 'party'
@@ -85,6 +86,20 @@ export class InvestmentMastersComponent {
'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;
@@ -251,6 +266,244 @@ export class InvestmentMastersComponent {
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;